(function (root, factory) { const service = factory(); if (typeof module === 'object' && module.exports) module.exports = service; if (root) root.ChannelVariantGrouping = service; })(typeof globalThis !== 'undefined' ? globalThis : this, function () { const QUALITY_TOKENS = new Set([ '4k', 'uhd', 'fhd', 'fullhd', 'hd', 'sd', 'hevc', 'h265', 'h264', '1080p', '1080i', '720p', '576p', '480p' ]); const REGION_PREFIX = /^(?:(?:\|\s*[a-z]{2,3}\s*\|)|(?:[a-z]{2,3}(?:-[a-z]{2,3})?\s*\|))\s*/i; function words(value) { return String(value || '') .normalize('NFKD') .replace(/[\u0300-\u036f]/g, '') .toLowerCase() .replace(REGION_PREFIX, '') .replace(/[|()[\]{}_.,:;+/\\-]+/g, ' ') .split(/\s+/) .filter(Boolean); } function normalizeChannelName(name) { return words(name).filter(token => !QUALITY_TOKENS.has(token)).join(' ').trim(); } function displayNameForFingerprint(fingerprint) { return fingerprint.split(' ').map(token => { if (token === 'bein') return 'beIN'; if (/^\d+$/.test(token)) return token; return token.charAt(0).toUpperCase() + token.slice(1); }).join(' '); } function getVariantKey(channel) { return `${channel.sourceId}:${channel.id}`; } function getQuality(channel) { const tokens = words(channel.name); if (tokens.includes('4k') || tokens.includes('uhd')) return '4K'; if (tokens.includes('fhd') || tokens.includes('fullhd') || tokens.includes('1080p') || tokens.includes('1080i')) return 'Full HD'; if (tokens.includes('hd') || tokens.includes('720p')) return 'HD'; if (tokens.includes('sd') || tokens.includes('576p') || tokens.includes('480p')) return 'SD'; return 'Standard'; } function qualityScore(channel) { return { '4K': 4, 'Full HD': 3, HD: 2, Standard: 1, SD: 0 }[getQuality(channel)] ?? 1; } function choosePreferredVariant(variants, options) { const selectedKey = options.selectedVariantKey; const selected = variants.find(variant => getVariantKey(variant) === selectedKey); if (selected && (options.showHidden || !options.isHidden(selected))) return selected; return variants.slice().sort((a, b) => { const hiddenDifference = Number(options.isHidden(a)) - Number(options.isHidden(b)); if (!options.showHidden && hiddenDifference) return hiddenDifference; return qualityScore(b) - qualityScore(a); })[0]; } function groupChannels(channels, options = {}) { const isFavorite = options.isFavorite || (() => false); const isHidden = options.isHidden || (() => false); const showHidden = Boolean(options.showHidden); const selectedVariants = options.selectedVariants || new Map(); const buckets = new Map(); channels.forEach(channel => { const fingerprint = normalizeChannelName(channel.name) || `channel-${getVariantKey(channel)}`; if (!buckets.has(fingerprint)) buckets.set(fingerprint, []); buckets.get(fingerprint).push(channel); }); return Array.from(buckets, ([fingerprint, variants]) => { const visibleVariants = variants.filter(variant => !isHidden(variant)); const preferredVariant = choosePreferredVariant(variants, { isHidden, showHidden, selectedVariantKey: selectedVariants.get(fingerprint) }); return { ...preferredVariant, id: `logical:${fingerprint}`, fingerprint, displayName: displayNameForFingerprint(fingerprint), name: displayNameForFingerprint(fingerprint), variants, visibleVariants, preferredVariant, selectedVariantId: getVariantKey(preferredVariant), isFavorite: variants.some(isFavorite), isHidden: visibleVariants.length === 0, groupTitle: preferredVariant.groupTitle, tvgLogo: preferredVariant.tvgLogo, tvgId: preferredVariant.tvgId }; }).filter(channel => showHidden || !channel.isHidden); } return { normalizeChannelName, displayNameForFingerprint, getVariantKey, getQuality, groupChannels }; });