/**
 * matchToChannel — fuzzy-match a sports-broadcaster name against the
 * user's loaded Xtream / M3U live-channel list, so a match card can
 * surface a "▶ Watch on [Channel]" deep-link.
 *
 * Strategy (cheap + good-enough):
 *   1. Normalise both sides — lower-case, strip punctuation, strip
 *      common channel-name fluff ("HD", "FHD", "4K", "+", "1080P",
 *      country-code prefixes like "[FR]", "FRA |", "AR ").
 *   2. Tokenise the broadcaster name ("beIN Sports 1" → ["bein",
 *      "sports", "1"]).
 *   3. Score each channel by counting how many broadcaster tokens
 *      appear as substrings of the normalised channel name. Bonus
 *      points when the channel contains the FULL broadcaster string.
 *   4. Return the highest-scoring channel if its score crosses the
 *      threshold (>= broadcaster tokens length); otherwise null.
 *
 * No Levenshtein — Levenshtein is slow on large playlists (10k+
 * channels common in M3U). Token-substring scoring is O(N · M) and
 * runs at 60fps on a 2020-era Smart TV.
 */

export type ChannelLike = {
  /** Channel id (Xtream stream_id, or m3u uri) — caller's choice.
   *  Returned verbatim so the caller can pass it back to `play()`. */
  id: string | number;
  name: string;
  /** Optional Xtream category for nicer UX hints — never used as a
   *  matching signal. */
  category?: string;
};

const FLUFF_TOKENS = [
  "hd", "fhd", "uhd", "4k", "1080p", "720p", "sd",
  "tv", "channel", "ch",
  "+", "plus",
];

function normalise(s: string): string {
  return s
    .toLowerCase()
    .normalize("NFKD")
    .replace(/[\u0300-\u036f]/g, "") // strip accents
    // strip leading "[XX]" / "XX |" / "XX -" / "XX:" country tags
    .replace(/^\s*[\[(]?[a-z]{2,3}[\])]?\s*[|:-]\s*/i, "")
    .replace(/[^\p{L}\p{N}\s]+/gu, " ") // punctuation → space
    .replace(/\s+/g, " ")
    .trim();
}

function tokens(s: string): string[] {
  return normalise(s)
    .split(/\s+/)
    .filter((t) => t.length > 0 && !FLUFF_TOKENS.includes(t));
}

/**
 * Find the best-matching channel for a broadcaster.
 * Returns null if no match passes the threshold.
 */
export function matchBroadcasterToChannel<T extends ChannelLike>(
  broadcaster: string | null | undefined,
  channels: readonly T[] | null | undefined,
): T | null {
  if (!broadcaster || !channels || channels.length === 0) return null;
  const bTokens = tokens(broadcaster);
  if (bTokens.length === 0) return null;
  const bNorm = normalise(broadcaster);

  let bestScore = 0;
  let best: T | null = null;

  for (const ch of channels) {
    const cNorm = normalise(ch.name);
    if (!cNorm) continue;

    // Quick win — full broadcaster string is a substring of the channel.
    let score = 0;
    if (cNorm.includes(bNorm)) score += bTokens.length * 2;

    // Token-by-token substring scoring.
    for (const tok of bTokens) {
      if (cNorm.includes(tok)) score += 1;
    }

    if (score > bestScore) {
      bestScore = score;
      best = ch;
    }
  }

  // Threshold — require AT LEAST as many token hits as the broadcaster
  // has tokens. Prevents matching "Sports Channel HD" to "beIN Sports
  // 1" just because both contain the word "sports".
  return bestScore >= bTokens.length ? best : null;
}
