export function normalizeLookupItem(item) {
	const derivedYear = Number(
		String(
			item?.year ??
			item?.ProductionYear ??
			item?.release_date ??
			item?.first_air_date ??
			''
		).slice(0, 4)
	) || null;
	const providerIds =
		item?.providerIds ||
		item?.ProviderIds ||
		(item?.tmdbId ? { Tmdb: String(item.tmdbId) } : {});

	return {
		id: String(item?.id ?? item?.Id ?? ''),
		name: item?.name || item?.Name || item?.SeriesName || item?.title || 'Unnamed item',
		type:
			item?.type ||
			item?.Type ||
			item?.CollectionType ||
			(item?.mediaType === 'tv' ? 'Series' : item?.mediaType === 'movie' ? 'Movie' : 'Item'),
		overview: item?.overview || item?.Overview || '',
		year: derivedYear,
		communityRating: item?.communityRating ?? item?.CommunityRating ?? item?.voteAverage ?? item?.vote_average ?? null,
		providerIds,
		genres: Array.isArray(item?.genres)
			? item.genres.filter(Boolean)
			: Array.isArray(item?.Genres)
				? item.Genres.filter(Boolean)
				: Array.isArray(item?.GenreItems)
					? item.GenreItems.map((genre) => genre?.Name).filter(Boolean)
				: [],
		parentId: item?.parentId ?? item?.ParentId ?? null
	};
}

const POSITIVE_GENRES = {
	Romance: 18,
	Comedy: 16,
	Drama: 10,
	Music: 4
};

const NEGATIVE_GENRES = {
	Animation: -30,
	Horror: -25,
	'Science Fiction': -20,
	Action: -16,
	Thriller: -14,
	Crime: -10,
	War: -18,
	Western: -14,
	Documentary: -18,
	Fantasy: -8,
	Family: -6
};

const POSITIVE_TERMS = [
	'wedding',
	'love',
	'romance',
	'relationship',
	'bride',
	'best friend',
	'friendship',
	'family',
	'holiday',
	'food',
	'small town',
	'bookstore',
	'restaurant',
	'writer',
	'divorce',
	'second chance',
	'starting over',
	'feel-good',
	'mother',
	'daughter',
	'sisters',
	'chosen family',
	'comedy of manners'
];

const NEGATIVE_TERMS = [
	'war',
	'serial killer',
	'murder spree',
	'mercenary',
	'zombie',
	'post-apocalyptic',
	'gang',
	'assassin',
	'combat',
	'superhero',
	'multiverse',
	'alien invasion',
	'dystopian',
	'dragon',
	'animated adventure'
];

const SEED_BONUS_TERMS = [
	'ensemble',
	'relationship',
	'family',
	'comedy',
	'romantic',
	'wedding',
	'friendship',
	'identity',
	'midlife',
	'second chance'
];

export const RECOMMENDATION_PROFILES = {
	balanced: {
		id: 'balanced',
		label: 'Balanced',
		description: 'Default Emby-style recommendation overlap ranking.'
	},
	classicComfort: {
		id: 'classicComfort',
		label: 'Classic Comfort',
		description: 'Bias toward older, warm, highly-rated movies and shows inspired by test.py.'
	}
};

function normalizeLimit(limit) {
	return Math.min(Math.max(Number(limit || 24), 1), 48);
}

function resolveRankingOptions(limitOrOptions) {
	if (typeof limitOrOptions === 'number' || limitOrOptions === undefined) {
		return {
			limit: normalizeLimit(limitOrOptions),
			profile: 'balanced',
			seeds: [],
			excludeIds: [],
			allowedTypes: []
		};
	}

	return {
		limit: normalizeLimit(limitOrOptions?.limit),
		profile: RECOMMENDATION_PROFILES[limitOrOptions?.profile] ? limitOrOptions.profile : 'balanced',
		seeds: Array.isArray(limitOrOptions?.seeds) ? limitOrOptions.seeds.map(normalizeLookupItem) : [],
		excludeIds: Array.isArray(limitOrOptions?.excludeIds)
			? limitOrOptions.excludeIds.map((id) => String(id).trim()).filter(Boolean)
			: [],
		allowedTypes: Array.isArray(limitOrOptions?.allowedTypes)
			? limitOrOptions.allowedTypes.map((type) => String(type).trim()).filter(Boolean)
			: []
	};
}

function genreBiasScore(genreNames, overview) {
	let score = 0;

	for (const genre of genreNames || []) {
		score += POSITIVE_GENRES[genre] || 0;
		score += NEGATIVE_GENRES[genre] || 0;
	}

	const text = String(overview || '').toLowerCase();
	for (const term of POSITIVE_TERMS) {
		if (text.includes(term)) score += 2;
	}
	for (const term of NEGATIVE_TERMS) {
		if (text.includes(term)) score -= 2;
	}

	return score;
}

function classicBonus(year, seedHits, overview, seedGenres) {
	let bonus = 0;

	if (year !== null && year !== undefined) {
		if (year >= 1985 && year <= 2008) bonus += 10;
		else if (year >= 2009 && year <= 2012) bonus += 4;
		else if (year < 1985) bonus += 2;
		else bonus -= 8;
	}

	bonus += Math.min(seedHits * 5, 20);

	const text = String(overview || '').toLowerCase();
	for (const term of SEED_BONUS_TERMS) {
		if (text.includes(term)) bonus += 1;
	}

	for (const genre of seedGenres) {
		if ((overview || '').toLowerCase().includes(genre.toLowerCase())) {
			bonus += 1;
		}
	}

	return bonus;
}

function buildSeedContext(seeds) {
	const genreHits = new Set();
	const mediaTypes = new Set();
	for (const seed of seeds || []) {
		for (const genre of seed.genres || []) {
			genreHits.add(genre);
		}
		if (seed.type) mediaTypes.add(seed.type);
	}
	return { genres: genreHits, mediaTypes };
}

function scoreSeedAffinity(item, seedContext) {
	const genres = new Set(item.genres || []);
	const text = `${item.name || ''} ${item.overview || ''}`.toLowerCase();
	let score = 0;

	for (const genre of seedContext.genres) {
		if (genres.has(genre)) score += 8;
	}

	const warmSeed =
		seedContext.genres.has('Romance') ||
		seedContext.genres.has('Drama') ||
		seedContext.genres.has('Comedy') ||
		seedContext.genres.has('Family');

	if (warmSeed) {
		if (genres.has('Romance')) score += 12;
		if (genres.has('Drama')) score += 10;
		if (genres.has('Comedy')) score += 8;
		if (genres.has('Family')) score += 5;
		if (genres.has('Horror')) score -= 28;
		if (genres.has('Science Fiction')) score -= 18;
		if (genres.has('Action')) score -= 16;
		if (genres.has('Thriller')) score -= 14;
		if (genres.has('War')) score -= 10;
		if (genres.has('Crime')) score -= 8;

		for (const term of POSITIVE_TERMS) {
			if (text.includes(term)) score += 1;
		}
		for (const term of NEGATIVE_TERMS) {
			if (text.includes(term)) score -= 2;
		}
	}

	return score;
}

function normalizeResultSet(resultSet) {
	if (Array.isArray(resultSet)) {
		return {
			items: resultSet,
			sourceWeight: 1
		};
	}

	return {
		items: Array.isArray(resultSet?.items) ? resultSet.items : [],
		sourceWeight: Math.max(1, Number(resultSet?.sourceWeight || 1))
	};
}

function evaluateClassicComfort(item, matches, seedContext) {
	const genres = item.genres || [];
	const year = Number.isFinite(Number(item.year)) ? Number(item.year) : null;
	const rating = Number.isFinite(Number(item.communityRating)) ? Number(item.communityRating) : null;
	const hasAnimation = genres.includes('Animation');

	if (hasAnimation) return { keep: false };
	if (year !== null && (year < 1980 || year > 2012)) return { keep: false };
	if (rating !== null && rating < 6.3) return { keep: false };

	const bias = genreBiasScore(genres, item.overview);
	const bonus = classicBonus(year, matches, `${item.name} ${item.overview}`, seedContext.genres);
	const styleScore = bias + bonus;

	if (styleScore < 16) {
		return { keep: false };
	}

	return {
		keep: true,
		styleScore,
		qualityScore: rating !== null ? Math.round(rating * 10) : null
	};
}

export function rankRecommendationResults(seedIds, resultSets, limitOrOptions = 24) {
	const options = resolveRankingOptions(limitOrOptions);
	const excluded = new Set([
		...(seedIds || []).map((id) => String(id).trim()),
		...options.excludeIds
	].filter(Boolean));
	const allowedTypes = new Set(options.allowedTypes);
	const scored = new Map();

	for (const resultSet of resultSets || []) {
		const normalizedSet = normalizeResultSet(resultSet);
		const seenInSet = new Set();
		const items = normalizedSet.items;
		for (let index = 0; index < items.length; index += 1) {
			const item = normalizeLookupItem(items[index]);
			if (!item.id || excluded.has(item.id)) continue;
			if (allowedTypes.size > 0 && !allowedTypes.has(item.type)) continue;
			if (seenInSet.has(item.id)) continue;
			seenInSet.add(item.id);

			const weight = Math.max(1, items.length - index) * normalizedSet.sourceWeight;
			const current = scored.get(item.id) || {
				item,
				score: 0,
				matches: 0,
				sourceStrength: 0,
				bestRank: Number.POSITIVE_INFINITY
			};

			current.item = item;
			current.score += weight;
			current.matches += 1;
			current.sourceStrength += normalizedSet.sourceWeight;
			current.bestRank = Math.min(current.bestRank, index);
			scored.set(item.id, current);
		}
	}

	const seedContext = buildSeedContext(options.seeds);

	return [...scored.values()]
		.map((entry) => {
			if (options.profile !== 'classicComfort') {
				const affinityScore = scoreSeedAffinity(entry.item, seedContext);
				return {
					...entry,
					affinityScore,
					totalScore:
						(entry.sourceStrength * 20) +
						entry.score +
						affinityScore,
					styleScore: null,
					qualityScore: entry.item.communityRating !== null
						? Math.round(Number(entry.item.communityRating) * 10)
						: null
				};
			}

			const style = evaluateClassicComfort(entry.item, entry.matches, seedContext);
			if (!style.keep) return null;

			return {
				...entry,
				styleScore: style.styleScore,
				qualityScore: style.qualityScore,
				totalScore:
					(entry.sourceStrength * 20) +
					entry.score +
					(style.styleScore * 2) +
					(style.qualityScore || 0)
			};
		})
		.filter(Boolean)
		.sort((a, b) => {
			if (b.totalScore !== a.totalScore) return b.totalScore - a.totalScore;
			if ((b.sourceStrength || 0) !== (a.sourceStrength || 0)) return (b.sourceStrength || 0) - (a.sourceStrength || 0);
			if (b.matches !== a.matches) return b.matches - a.matches;
			if ((b.styleScore || 0) !== (a.styleScore || 0)) return (b.styleScore || 0) - (a.styleScore || 0);
			if ((b.qualityScore || 0) !== (a.qualityScore || 0)) return (b.qualityScore || 0) - (a.qualityScore || 0);
			if (b.score !== a.score) return b.score - a.score;
			if (a.bestRank !== b.bestRank) return a.bestRank - b.bestRank;
			return a.item.name.localeCompare(b.item.name);
		})
		.slice(0, options.limit)
		.map((entry) => ({
			...entry.item,
			matchCount: entry.matches,
			score: entry.score,
			totalScore: entry.totalScore,
			sourceStrength: entry.sourceStrength,
			affinityScore: entry.affinityScore ?? null,
			styleScore: entry.styleScore,
			qualityScore: entry.qualityScore
		}));
}
