<?php
@ini_set('memory_limit', '256M');
@ini_set('max_execution_time', '30');
header('Access-Control-Allow-Origin: *');

$USER = 'base';
$PASS = 'base';
$BASE = 'https://apps.spandaman.co.uk/spandaman/tmdbbase';
$ACTION = isset($_GET['action']) ? trim((string)$_GET['action']) : '';
$TMDB_KEY = 'ab083c1bc18d15b25e9a2680cf6f745b';
$TMDB_LANG = 'en-US';

function sp_json($data) {
	header('Content-Type: application/json');
	echo json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
	exit();
}

function sp_auth_ok($user, $pass) {
	if (!isset($_GET['username']) && !isset($_GET['password'])) return true;
	return (($_GET['username'] ?? '') === $user && ($_GET['password'] ?? '') === $pass);
}

function sp_auth_fail() {
	sp_json(array(
		'user_info' => array('auth' => 0, 'status' => 'Disabled', 'message' => 'Invalid username or password'),
		'server_info' => array('url' => 'apps.spandaman.co.uk', 'timestamp_now' => time(), 'time_now' => date('Y-m-d H:i:s'))
	));
}

function sp_redirect_json($base, $filename) {
	$path = __DIR__ . '/' . $filename;
	if (!is_file($path) || filesize($path) <= 2) sp_json(array());
	header('Content-Type: application/json');
	header('Cache-Control: public, max-age=60');
	header('Location: ' . rtrim($base, '/') . '/' . rawurlencode($filename), true, 302);
	exit();
}

function sp_stream_file($path, $contentType = 'application/json') {
	if (!is_file($path) || filesize($path) <= 2) sp_json(array());
	while (ob_get_level() > 0) @ob_end_clean();
	header('Content-Type: ' . $contentType);
	$fh = fopen($path, 'rb');
	if (!$fh) sp_json(array());
	while (!feof($fh)) {
		echo fread($fh, 262144);
		flush();
	}
	fclose($fh);
	exit();
}

function sp_read_json_small($filename) {
	$path = __DIR__ . '/' . $filename;
	if (!is_file($path) || filesize($path) <= 2 || filesize($path) > 10485760) return array();
	$raw = file_get_contents($path);
	if (!is_string($raw)) return array();
	if (substr($raw, 0, 3) === "\xEF\xBB\xBF") $raw = substr($raw, 3);
	$data = json_decode($raw, true);
	return is_array($data) ? $data : array();
}

function sp_find_by_id_in_small_map($filename, $id) {
	$data = sp_read_json_small($filename);
	$key = (string)$id;
	if (isset($data[$key]) && is_array($data[$key])) return $data[$key];
	foreach ($data as $row) {
		if (is_array($row)) {
			$rid = (string)($row['stream_id'] ?? ($row['series_id'] ?? ($row['id'] ?? '')));
			if ($rid === $key) return $row;
		}
	}
	return array();
}

function sp_tmdb_img($path) {
	if (!is_string($path) || $path === '') return '';
	if (preg_match('~^https?://~i', $path)) return $path;
	return 'https://image.tmdb.org/t/p/original' . $path;
}

function sp_http_json($url, $timeout = 18) {
	$ctx = stream_context_create(array('http' => array(
		'timeout' => $timeout,
		'header' => "User-Agent: SPANDAMAN-TMDBBASE/1.0\r\nAccept: application/json\r\n"
	)));
	$raw = @file_get_contents($url, false, $ctx);
	if (!is_string($raw) || $raw === '') return array();
	$data = json_decode($raw, true);
	return is_array($data) ? $data : array();
}

function sp_json_object_from_buffer($buffer, $start) {
	$depth = 0;
	$inString = false;
	$escape = false;
	$len = strlen($buffer);
	for ($i = $start; $i < $len; $i++) {
		$ch = $buffer[$i];
		if ($inString) {
			if ($escape) {
				$escape = false;
			} elseif ($ch === '\\') {
				$escape = true;
			} elseif ($ch === '"') {
				$inString = false;
			}
			continue;
		}
		if ($ch === '"') {
			$inString = true;
			continue;
		}
		if ($ch === '{') {
			$depth++;
		} elseif ($ch === '}') {
			$depth--;
			if ($depth === 0) {
				return substr($buffer, $start, $i - $start + 1);
			}
		}
	}
	return null;
}

function sp_find_json_row_by_id_stream($filename, $id, $idKeys = array('stream_id', 'series_id', 'id', 'num')) {
	$path = __DIR__ . '/' . $filename;
	if (!is_file($path) || filesize($path) <= 2) return array();
	$fh = fopen($path, 'rb');
	if (!$fh) return array();
	$target = (string)$id;
	$needles = array();
	foreach ($idKeys as $key) {
		$needles[] = '"' . $key . '":' . $target;
		$needles[] = '"' . $key . '": ' . $target;
		$needles[] = '"' . $key . '":"' . $target . '"';
		$needles[] = '"' . $key . '": "' . $target . '"';
	}
	$buffer = '';
	$maxKeep = 2097152;
	$maxObject = 4194304;
	while (($chunk = fread($fh, 1048576)) !== false && $chunk !== '') {
		$buffer .= $chunk;
		$hit = false;
		foreach ($needles as $needle) {
			if (strpos($buffer, $needle) !== false) {
				$hit = true;
				break;
			}
		}
		if ($hit) {
			foreach ($needles as $needle) {
				$pos = strpos($buffer, $needle);
				if ($pos === false) continue;
				$start = strrpos(substr($buffer, 0, $pos), '{');
				if ($start === false) continue;
				while (strlen($buffer) - $start < $maxObject) {
					$json = sp_json_object_from_buffer($buffer, $start);
					if ($json !== null) {
						$row = json_decode($json, true);
						if (is_array($row)) {
							foreach ($idKeys as $key) {
								if ((string)($row[$key] ?? '') === $target) {
									fclose($fh);
									return $row;
								}
							}
						}
						break;
					}
					$more = fread($fh, 1048576);
					if ($more === false || $more === '') break;
					$buffer .= $more;
				}
			}
		}
		if (strlen($buffer) > $maxKeep) {
			$buffer = substr($buffer, -$maxKeep);
		}
	}
	fclose($fh);
	return array();
}

function sp_series_row_by_id($id) {
	$row = sp_find_json_row_by_id_stream('spandaman_public_series_player_streams.json', $id, array('series_id', 'id', 'num'));
	if (!empty($row)) return $row;
	$row = sp_find_json_row_by_id_stream('spandaman_series_player_streams.json', $id, array('series_id', 'id', 'num'));
	return is_array($row) ? $row : array();
}

function sp_vod_row_by_id($id) {
	$row = sp_find_json_row_by_id_stream('spandaman_public_vod_player_streams.json', $id, array('stream_id', 'id', 'num'));
	if (!empty($row)) return $row;
	$row = sp_find_json_row_by_id_stream('spandaman_extra_verified_streams.json', $id, array('id', 'stream_id', 'num'));
	if (!empty($row)) return $row;
	$row = sp_find_json_row_by_id_stream('channels/spandaman_extra_verified_streams.json', $id, array('id', 'stream_id', 'num'));
	if (!empty($row)) return $row;
	$row = sp_find_json_row_by_id_stream('adult-movies.json', $id, array('stream_id', 'num', 'id'));
	if (!empty($row)) return $row;
	$row = sp_find_json_row_by_id_stream('playlist.json', $id, array('stream_id', 'num', 'id'));
	if (!empty($row)) return $row;
	return sp_find_json_row_by_id_stream('spandaman_vod_player_streams.json', $id, array('stream_id', 'num', 'id'));
}

function sp_mapped_series_episodes($seriesId) {
	$seriesId = (string)$seriesId;
	if ($seriesId === '') return array();
	$paths = array(__DIR__ . '/spandaman_series_episode_map_grouped.json', __DIR__ . '/spandaman_series_episode_map.json', __DIR__ . '/channels/spandaman_series_episode_map.json');
	$out = array();
	foreach ($paths as $path) {
		if (!is_file($path) || filesize($path) <= 2 || filesize($path) > 16777216) continue;
		$data = json_decode((string)@file_get_contents($path), true);
		if (!is_array($data)) continue;
		foreach ($data as $episodeId => $entry) {
			$matchedSeries = '';
			$seasonNum = 1;
			$episodeNum = 1;
			if (is_array($entry) && isset($entry[0]) && is_array($entry[0])) {
				if ((string)$episodeId !== $seriesId) continue;
				foreach ($entry as $episodeRow) {
					if (!is_array($episodeRow)) continue;
					$mappedEpisodeId = (string)($episodeRow['episode_id'] ?? ($episodeRow['id'] ?? ''));
					if ($mappedEpisodeId === '' || !ctype_digit($mappedEpisodeId)) continue;
					$out[] = array(
						'episode_id' => $mappedEpisodeId,
						'season' => max(1, intval($episodeRow['season'] ?? ($episodeRow['season_number'] ?? 1))),
						'episode' => max(1, intval($episodeRow['episode'] ?? ($episodeRow['episode_num'] ?? ($episodeRow['episode_number'] ?? 1)))),
					);
				}
				continue;
			}
			if (is_string($entry) && $entry !== '') {
				$decoded = base64_decode($entry, true);
				if (!is_string($decoded)) continue;
				if (preg_match('~:(\d+)/season/(\d+)/episode/(\d+)~', $decoded, $m)) {
					$matchedSeries = (string)$m[1];
					$seasonNum = max(1, intval($m[2]));
					$episodeNum = max(1, intval($m[3]));
				}
			} elseif (is_array($entry)) {
				$matchedSeries = (string)($entry['series_id'] ?? '');
				$seasonNum = max(1, intval($entry['season'] ?? ($entry['season_number'] ?? 1)));
				$episodeNum = max(1, intval($entry['episode'] ?? ($entry['episode_num'] ?? ($entry['episode_number'] ?? 1))));
			}
			if ($matchedSeries !== $seriesId) continue;
			$episodeId = (string)$episodeId;
			if ($episodeId === '' || !ctype_digit($episodeId)) continue;
			$out[] = array('episode_id' => $episodeId, 'season' => $seasonNum, 'episode' => $episodeNum);
		}
		if (!empty($out)) break;
	}
	usort($out, function ($a, $b) {
		$seasonCmp = intval($a['season'] ?? 0) <=> intval($b['season'] ?? 0);
		if ($seasonCmp !== 0) return $seasonCmp;
		return intval($a['episode'] ?? 0) <=> intval($b['episode'] ?? 0);
	});
	return $out;
}

function sp_merge_series_episode_map($map) {
	if (empty($map) || !is_array($map)) return;
	$path = __DIR__ . '/spandaman_series_episode_map.json';
	$current = array();
	if (is_file($path) && filesize($path) > 2 && filesize($path) < 16777216) {
		$tmp = json_decode((string)@file_get_contents($path), true);
		if (is_array($tmp)) $current = $tmp;
	}
	foreach ($map as $k => $v) {
		if ($k !== '' && $v !== '') $current[(string)$k] = $v;
	}
	@file_put_contents($path, json_encode($current, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), LOCK_EX);
	@chmod($path, 0777);
}

if (!sp_auth_ok($USER, $PASS)) sp_auth_fail();

if ($ACTION === '') {
	$now = time();
	sp_json(array(
		'user_info' => array(
			'username' => $_GET['username'] ?? $USER,
			'password' => $_GET['password'] ?? $PASS,
			'message' => '',
			'auth' => 1,
			'status' => 'Active',
			'exp_date' => '4095101905',
			'is_trial' => '0',
			'active_cons' => '0',
			'created_at' => '1684851647',
			'max_connections' => '1000',
			'allowed_output_formats' => array('m3u8', 'ts')
		),
		'server_info' => array(
			'url' => 'apps.spandaman.co.uk',
			'port' => '80',
			'https_port' => '',
			'server_protocol' => 'http',
			'rtmp_port' => '',
			'timezone' => 'Europe/London',
			'timestamp_now' => $now,
			'time_now' => date('Y-m-d H:i:s', $now)
		)
	));
}

$fullCatalog = !isset($_GET['full']) || $_GET['full'] !== '0';
$usePublic = false;
if (isset($_GET['public'])) {
	$usePublic = $_GET['public'] !== '0' && $_GET['public'] !== 'false';
}
if ($ACTION === 'get_live_streams') sp_redirect_json($BASE, 'spandaman_live_player_streams.json');
if ($ACTION === 'get_vod_streams') sp_redirect_json($BASE, $usePublic ? 'spandaman_public_vod_player_streams.json' : 'spandaman_vod_player_streams.json');
if ($ACTION === 'get_series') sp_redirect_json($BASE, $usePublic ? 'spandaman_public_series_player_streams.json' : 'spandaman_series_player_streams.json');

if ($ACTION === 'get_live_categories') sp_redirect_json($BASE, 'spandaman_live_player_categories.json');
if ($ACTION === 'get_vod_categories') sp_redirect_json($BASE, $usePublic ? 'spandaman_public_vod_player_categories.json' : 'spandaman_vod_player_categories.json');
if ($ACTION === 'get_series_categories') sp_redirect_json($BASE, $usePublic ? 'spandaman_public_series_player_categories.json' : 'spandaman_series_player_categories.json');

if ($ACTION === 'get_vod_info') {
	$id = (string)($_GET['vod_id'] ?? '');
	$row = sp_vod_row_by_id($id);
	$name = (string)($row['name'] ?? ($row['title'] ?? ('VOD ' . $id)));
	$icon = (string)($row['stream_icon'] ?? ($row['cover'] ?? ($row['movie_image'] ?? ($row['poster'] ?? ($row['logo'] ?? '')))));
	if ($icon === '' && isset($row['poster_path'])) $icon = sp_tmdb_img($row['poster_path']);
	$plot = (string)($row['plot'] ?? ($row['description'] ?? ($row['overview'] ?? '')));
	$rating = (string)($row['rating'] ?? '');
	$genre = (string)($row['genres'] ?? ($row['genre'] ?? ($row['group'] ?? '')));
	$direct = (string)($row['direct_source'] ?? ($row['url'] ?? ''));
	if ($direct === '' || strpos($direct, '/movie/base/base/') === false) $direct = $BASE . '/movie/base/base/' . rawurlencode($id) . '.mp4?resolve=1';
	sp_json(array(
		'info' => array(
			'name' => $name,
			'movie_image' => $icon,
			'cover_big' => $icon,
			'cover' => $icon,
			'plot' => $plot,
			'description' => $plot,
			'genre' => $genre,
			'cast' => (string)($row['cast'] ?? ''),
			'director' => (string)($row['director'] ?? ''),
			'rating' => $rating,
			'releasedate' => (string)($row['releaseDate'] ?? ($row['release_date'] ?? '')),
			'duration_secs' => (int)($row['duration_secs'] ?? 0),
			'duration' => (string)($row['duration'] ?? ''),
			'backdrop_path' => isset($row['backdrop_path']) ? (array)$row['backdrop_path'] : array()
		),
		'movie_data' => array(
			'stream_id' => is_numeric($id) ? (int)$id : $id,
			'name' => $name,
			'added' => (string)($row['added'] ?? time()),
			'category_id' => (string)($row['category_id'] ?? '878'),
			'container_extension' => (string)($row['container_extension'] ?? 'mp4'),
			'direct_source' => $direct
		)
	));
}

if ($ACTION === 'get_series_info') {
	global $TMDB_KEY, $TMDB_LANG, $BASE;
	$id = (string)($_GET['series_id'] ?? '');
	$mappedEpisodes = sp_mapped_series_episodes($id);
	$cache = sp_find_by_id_in_small_map('spandaman_series_info_index.json', $id);
	if (!empty($cache) && empty($mappedEpisodes)) sp_json($cache);
	$series = sp_series_row_by_id($id);
	$name = (string)($series['name'] ?? ('Series ' . $id));
	$cover = (string)($series['cover'] ?? ($series['stream_icon'] ?? ''));
	$details = sp_http_json('https://api.themoviedb.org/3/tv/' . rawurlencode($id) . '?api_key=' . rawurlencode($TMDB_KEY) . '&include_adult=true&append_to_response=external_ids,credits&language=' . rawurlencode($TMDB_LANG), 20);
	if (empty($details)) {
		if (!empty($mappedEpisodes)) {
			$episodes = array();
			$seasons = array();
			foreach ($mappedEpisodes as $mapped) {
				$seasonNumber = max(1, intval($mapped['season'] ?? 1));
				$episodeNumber = max(1, intval($mapped['episode'] ?? 1));
				$episodeId = (string)($mapped['episode_id'] ?? '');
				if ($episodeId === '') continue;
				if (!isset($episodes[(string)$seasonNumber])) $episodes[(string)$seasonNumber] = array();
				if (!isset($seasons[$seasonNumber])) {
					$seasons[$seasonNumber] = array('air_date' => '', 'episode_count' => 0, 'id' => $seasonNumber, 'name' => 'Season ' . $seasonNumber, 'season_number' => $seasonNumber, 'cover' => $cover, 'cover_big' => $cover);
				}
				$seasons[$seasonNumber]['episode_count']++;
				$episodes[(string)$seasonNumber][] = array(
					'id' => is_numeric($episodeId) ? (int)$episodeId : $episodeId,
					'episode_num' => $episodeNumber,
					'title' => 'S' . str_pad((string)$seasonNumber, 2, '0', STR_PAD_LEFT) . 'E' . str_pad((string)$episodeNumber, 2, '0', STR_PAD_LEFT),
					'container_extension' => 'mp4',
					'added' => (string)time(),
					'season' => $seasonNumber,
					'direct_source' => $BASE . '/series/base/base/' . rawurlencode($episodeId) . '.mp4?resolve=1',
					'info' => array('tmdb_id' => is_numeric($id) ? (int)$id : $id, 'name' => '', 'cover_big' => $cover, 'movie_image' => $cover, 'plot' => '')
				);
			}
			ksort($seasons);
			sp_json(array(
				'seasons' => array_values($seasons),
				'info' => array('name' => $name, 'cover' => $cover, 'cover_big' => $cover, 'plot' => (string)($series['plot'] ?? ''), 'rating' => (string)($series['rating'] ?? '')),
				'episodes' => $episodes
			));
		}
		sp_json(array(
			'seasons' => array(array('air_date' => '', 'episode_count' => 0, 'id' => 1, 'name' => 'Season 1', 'season_number' => 1)),
			'info' => array('name' => $name, 'cover' => $cover, 'cover_big' => $cover, 'plot' => (string)($series['plot'] ?? ''), 'rating' => (string)($series['rating'] ?? '')),
			'episodes' => array()
		));
	}
	$totalSeasons = max(0, (int)($details['number_of_seasons'] ?? 0));
	$full = $details;
	for ($start = 1; $start <= $totalSeasons; $start += 20) {
		$end = min($start + 19, $totalSeasons);
		$parts = array();
		for ($s = $start; $s <= $end; $s++) $parts[] = 'season/' . $s;
		$batch = sp_http_json('https://api.themoviedb.org/3/tv/' . rawurlencode($id) . '?api_key=' . rawurlencode($TMDB_KEY) . '&append_to_response=' . rawurlencode(implode(',', $parts)) . '&language=' . rawurlencode($TMDB_LANG), 25);
		foreach ($parts as $part) {
			if (isset($batch[$part]) && is_array($batch[$part])) $full[$part] = $batch[$part];
		}
	}
	$poster = sp_tmdb_img($full['poster_path'] ?? '');
	if ($poster === '') $poster = $cover;
	$backdrop = sp_tmdb_img($full['backdrop_path'] ?? '');
	$genres = array();
	foreach (($full['genres'] ?? array()) as $g) if (isset($g['name'])) $genres[] = $g['name'];
	$cast = array();
	foreach (($details['credits']['cast'] ?? array()) as $actor) {
		if (($actor['known_for_department'] ?? '') === 'Acting' && isset($actor['name'])) $cast[] = $actor['name'];
		if (count($cast) >= 4) break;
	}
	$result = array(
		'seasons' => array(),
		'info' => array(
			'name' => (string)($full['name'] ?? $name),
			'cover' => $poster,
			'cover_big' => $poster,
			'plot' => (string)($full['overview'] ?? ($series['plot'] ?? '')),
			'cast' => implode(', ', $cast),
			'director' => (string)($full['created_by'][0]['name'] ?? ''),
			'genre' => implode(', ', $genres),
			'releaseDate' => (string)($full['first_air_date'] ?? ''),
			'last_modified' => strtotime((string)($full['last_air_date'] ?? '')) ?: time(),
			'rating' => (string)($full['vote_average'] ?? ($series['rating'] ?? '')),
			'rating_5based' => (float)($full['vote_average'] ?? 0) / 2,
			'backdrop_path' => $backdrop ? array($backdrop) : array(),
			'youtube_trailer' => '',
			'episode_run_time' => (int)($full['episode_run_time'][0] ?? 0),
			'category_id' => (string)($series['category_id'] ?? '')
		),
		'episodes' => array()
	);
	if (!empty($mappedEpisodes)) {
		$tmdbLookup = array();
		for ($seasonNumber = 1; $seasonNumber <= $totalSeasons; $seasonNumber++) {
			$key = 'season/' . $seasonNumber;
			if (!isset($full[$key]) || !is_array($full[$key])) continue;
			foreach ((array)($full[$key]['episodes'] ?? array()) as $episode) {
				if (!is_array($episode)) continue;
				$episodeNum = (int)($episode['episode_number'] ?? 0);
				if ($episodeNum > 0) $tmdbLookup[$seasonNumber . ':' . $episodeNum] = $episode;
			}
		}
		$seasonCounts = array();
		foreach ($mappedEpisodes as $mapped) {
			$seasonNumber = max(1, intval($mapped['season'] ?? 1));
			$episodeNumber = max(1, intval($mapped['episode'] ?? 1));
			$episodeId = (string)($mapped['episode_id'] ?? '');
			if ($episodeId === '') continue;
			$episode = $tmdbLookup[$seasonNumber . ':' . $episodeNumber] ?? array();
			$air = strtotime((string)($episode['air_date'] ?? ''));
			$still = sp_tmdb_img($episode['still_path'] ?? '');
			if ($still === '') $still = $backdrop ?: $poster;
			if (!isset($result['episodes'][(string)$seasonNumber])) $result['episodes'][(string)$seasonNumber] = array();
			$result['episodes'][(string)$seasonNumber][] = array(
				'id' => is_numeric($episodeId) ? (int)$episodeId : $episodeId,
				'episode_num' => $episodeNumber,
				'title' => 'S' . str_pad((string)$seasonNumber, 2, '0', STR_PAD_LEFT) . 'E' . str_pad((string)$episodeNumber, 2, '0', STR_PAD_LEFT) . ' - ' . (string)($episode['name'] ?? ''),
				'container_extension' => 'mp4',
				'custom_sid' => '',
				'added' => (string)($air ?: time()),
				'season' => $seasonNumber,
				'direct_source' => $BASE . '/series/base/base/' . rawurlencode($episodeId) . '.mp4?resolve=1',
				'info' => array(
					'tmdb_id' => is_numeric($id) ? (int)$id : $id,
					'name' => (string)($episode['name'] ?? ''),
					'cover_big' => $still,
					'movie_image' => $still,
					'plot' => (string)($episode['overview'] ?? '')
				)
			);
			$seasonCounts[$seasonNumber] = ($seasonCounts[$seasonNumber] ?? 0) + 1;
		}
		foreach ($seasonCounts as $seasonNumber => $count) {
			$result['seasons'][] = array(
				'air_date' => '',
				'episode_count' => $count,
				'id' => (string)$seasonNumber,
				'name' => 'Season ' . $seasonNumber,
				'overview' => '',
				'season_number' => $seasonNumber,
				'backdrop_path' => $backdrop,
				'cover' => $poster,
				'cover_big' => $poster
			);
		}
		sp_json($result);
	}
	$routeMap = array();
	$today = strtotime('today');
	for ($seasonNumber = 1; $seasonNumber <= $totalSeasons; $seasonNumber++) {
		$key = 'season/' . $seasonNumber;
		if (!isset($full[$key]) || !is_array($full[$key])) continue;
		$seasonData = $full[$key];
		$episodes = is_array($seasonData['episodes'] ?? null) ? $seasonData['episodes'] : array();
		$result['seasons'][] = array(
			'air_date' => (string)($seasonData['air_date'] ?? ''),
			'episode_count' => count($episodes),
			'id' => (string)($seasonData['_id'] ?? $seasonNumber),
			'name' => 'Season ' . $seasonNumber,
			'overview' => (string)($seasonData['overview'] ?? ''),
			'season_number' => $seasonNumber,
			'backdrop_path' => $backdrop,
			'cover' => $poster,
			'cover_big' => $poster
		);
		foreach ($episodes as $episode) {
			$air = strtotime((string)($episode['air_date'] ?? ''));
			if ($air && $air > $today) continue;
			$episodeId = (string)($episode['id'] ?? '');
			if ($episodeId === '') continue;
			$epNum = (int)($episode['episode_number'] ?? 0);
			$token = base64_encode((string)($details['external_ids']['imdb_id'] ?? ('tmdb' . $id)) . ':' . $id . '/season/' . $seasonNumber . '/episode/' . $epNum);
			$routeMap[$episodeId] = $token;
			$still = sp_tmdb_img($episode['still_path'] ?? '');
			if ($still === '') $still = $backdrop ?: $poster;
			if (!isset($result['episodes'][(string)$seasonNumber])) $result['episodes'][(string)$seasonNumber] = array();
			$result['episodes'][(string)$seasonNumber][] = array(
				'id' => is_numeric($episodeId) ? (int)$episodeId : $episodeId,
				'episode_num' => $epNum,
				'title' => 'S' . str_pad((string)$seasonNumber, 2, '0', STR_PAD_LEFT) . 'E' . str_pad((string)$epNum, 2, '0', STR_PAD_LEFT) . ' - ' . (string)($episode['name'] ?? ''),
				'container_extension' => 'mp4',
				'custom_sid' => '',
				'added' => (string)($air ?: time()),
				'season' => $seasonNumber,
				'direct_source' => $BASE . '/series/base/base/' . rawurlencode($episodeId) . '.mp4?resolve=1',
				'info' => array(
					'tmdb_id' => is_numeric($id) ? (int)$id : $id,
					'name' => (string)($episode['name'] ?? ''),
					'cover_big' => $still,
					'movie_image' => $still,
					'plot' => (string)($episode['overview'] ?? '')
				)
			);
		}
	}
	sp_merge_series_episode_map($routeMap);
	sp_json($result);
}

sp_json(array());
