// Pure helpers for detecting embeddable media URLs and building their embed
// URLs. No React/DOM/CSS imports so this stays unit-testable in isolation.
//
// "kind" drives how the card lays the embed out:
//   landscape → 16:9 video      portrait → 9:16 video
//   audio     → short fixed-height player (Spotify/SoundCloud/Tidal/Apple Music)
//   rich      → tall fixed-height post embed (Instagram / Reddit)

export type EmbedKind = 'landscape' | 'portrait' | 'audio' | 'rich';

export type MediaEmbed = {
  provider: string;
  kind: EmbedKind;
  embedUrl: string;
  /** Fixed pixel height for audio/rich embeds (aspect-ratio is used for video). */
  height?: number;
};

// --- YouTube --------------------------------------------------------------

const YOUTUBE_HOSTS = ['www.youtube.com', 'youtube.com', 'm.youtube.com', 'music.youtube.com'];

export function getYouTubeVideoId(url: string): string | null {
  try {
    const { hostname, pathname, searchParams } = new URL(url);
    if (hostname === 'youtu.be') return pathname.slice(1).split('/')[0] || null;
    if (YOUTUBE_HOSTS.includes(hostname)) {
      if (pathname === '/watch') return searchParams.get('v');
      const m =
        pathname.match(/^\/embed\/([A-Za-z0-9_-]+)/) ||
        pathname.match(/^\/live\/([A-Za-z0-9_-]+)/) ||
        pathname.match(/^\/shorts\/([A-Za-z0-9_-]+)/);
      if (m) return m[1];
    }
  } catch {
    /* ignore */
  }
  return null;
}

export function isYouTubeShorts(url: string): boolean {
  try {
    const { hostname, pathname } = new URL(url);
    if (!YOUTUBE_HOSTS.includes(hostname)) return false;
    return /^\/shorts\/[A-Za-z0-9_-]+/.test(pathname);
  } catch {
    return false;
  }
}

export function getYoutubeShortsId(url: string): string | null {
  try {
    const { hostname, pathname } = new URL(url);
    if (!YOUTUBE_HOSTS.includes(hostname)) return null;
    const m = pathname.match(/^\/shorts\/([A-Za-z0-9_-]+)/);
    return m ? m[1] : null;
  } catch {
    return null;
  }
}

// --- Vimeo ----------------------------------------------------------------

/** Vimeo id + optional private/unlisted hash (vimeo.com/{id}/{hash}). */
export function getVimeoParts(url: string): { id: string; hash?: string } | null {
  try {
    const { hostname, pathname } = new URL(url);
    if (hostname !== 'vimeo.com' && hostname !== 'www.vimeo.com') return null;
    // Canonical /{id} or unlisted /{id}/{hash}. The hash is a lowercase-hex token
    // (constrain it so a normal video's trailing segment — /likes, /settings, a
    // review slug — isn't captured as a bogus `h=` param that Vimeo then rejects).
    let m = pathname.match(/^\/(\d+)(?:\/([0-9a-f]{6,}))?/);
    if (m) return { id: m[1], hash: m[2] };
    // channels/groups/album share a trailing numeric video id
    m = pathname.match(/\/(?:channels\/[^/]+|groups\/[^/]+\/videos|album\/[^/]+\/video)\/(\d+)/);
    if (m) return { id: m[1] };
    return null;
  } catch {
    return null;
  }
}

export function getVimeoVideoId(url: string): string | null {
  return getVimeoParts(url)?.id ?? null;
}

// --- TikTok ---------------------------------------------------------------

/** Canonical /video/<id> links only; short links resolve via oEmbed (below). */
export function getTikTokVideoId(url: string): string | null {
  try {
    const { hostname, pathname } = new URL(url);
    const h = hostname.replace(/^www\./, '');
    if (h !== 'tiktok.com') return null;
    const m = pathname.match(/\/video\/(\d+)/);
    return m ? m[1] : null;
  } catch {
    return null;
  }
}

/** Any embeddable TikTok video link — canonical, short (/t/, /v/), or vm/vt.tiktok.com. */
export function isTikTokLink(url: string): boolean {
  try {
    const { hostname, pathname } = new URL(url);
    const h = hostname.replace(/^www\./, '');
    if (h === 'vm.tiktok.com' || h === 'vt.tiktok.com') return true;
    if (h === 'tiktok.com') return /^\/(t\/|v\/|embed\/|@[^/]+\/video\/)/.test(pathname);
    return false;
  } catch {
    return false;
  }
}

export function tiktokOembedUrl(url: string): string {
  return `https://www.tiktok.com/oembed?url=${encodeURIComponent(url)}`;
}

/** Extract the numeric video id from a TikTok oEmbed JSON response. */
export function tiktokIdFromOembed(data: {
  embed_product_id?: unknown;
  html?: unknown;
}): string | null {
  const pid = String(data.embed_product_id ?? '').match(/\d+/)?.[0];
  if (pid) return pid;
  if (typeof data.html === 'string') {
    return (
      data.html.match(/data-video-id="(\d+)"/)?.[1] ??
      data.html.match(/\/video\/(\d+)/)?.[1] ??
      null
    );
  }
  return null;
}

/**
 * Read a content height out of the postMessage shapes used by the resizable
 * embeds. Shapes verified against the providers' live 2026 embed scripts:
 *   Instagram: { type: 'MEASURE', details: { height } }
 *   Reddit:    { type: 'resize.embed', data: <height> }
 *   Twitter/X: { 'twttr.embed': [{ method: 'twttr.private.resize', params: [{ height }] }] }
 */
export function extractEmbedHeight(data: unknown): number | undefined {
  if (!data || typeof data !== 'object') return undefined;
  const d = data as {
    type?: string;
    height?: number;
    data?: unknown;
    details?: { height?: number };
    'twttr.embed'?: unknown;
  };
  if (d.type === 'MEASURE' && typeof d.details?.height === 'number') return d.details.height;
  if (d.type === 'resize.embed' && typeof d.data === 'number') return d.data;
  const tw = d['twttr.embed'];
  if (tw) {
    const calls = (Array.isArray(tw) ? tw : [tw]) as Array<{
      method?: string;
      params?: Array<{ height?: number }>;
    }>;
    for (let i = 0; i < calls.length; i += 1) {
      const c = calls[i];
      if (c?.method === 'twttr.private.resize' && typeof c.params?.[0]?.height === 'number') {
        return c.params[0].height;
      }
    }
  }
  if (typeof d.height === 'number') return d.height;
  return undefined;
}

export function tiktokPlayerEmbedUrl(id: string): string {
  // Pure 9:16 video player. music_info/description default OFF (they'd switch
  // TikTok to a wide "video + info panel" layout); controls/progress/play/volume/
  // fullscreen buttons all default ON, so only autoplay + rel are load-bearing.
  return `https://www.tiktok.com/player/v1/${encodeURIComponent(id)}?autoplay=1&rel=0`;
}

// --- Dailymotion ----------------------------------------------------------

export function getDailymotionId(url: string): string | null {
  try {
    const { hostname, pathname } = new URL(url);
    const h = hostname.replace(/^www\./, '');
    if (h === 'dai.ly') return pathname.slice(1).split('/')[0] || null;
    if (h === 'dailymotion.com') {
      const m = pathname.match(/^\/video\/([A-Za-z0-9]+)/);
      return m ? m[1] : null;
    }
  } catch {
    /* ignore */
  }
  return null;
}

// --- Streamable -----------------------------------------------------------

// Streamable's own utility/first-path pages that are not video ids.
const STREAMABLE_RESERVED = new Set([
  'e',
  'login',
  'signup',
  'settings',
  'account',
  'dashboard',
  'help',
  'terms',
  'privacy',
  'about',
]);

export function getStreamableId(url: string): string | null {
  try {
    const { hostname, pathname } = new URL(url);
    if (hostname.replace(/^www\./, '') !== 'streamable.com') return null;
    const m = pathname.match(/^\/([A-Za-z0-9]+)/);
    return m && !STREAMABLE_RESERVED.has(m[1].toLowerCase()) ? m[1] : null;
  } catch {
    return null;
  }
}

// --- Steam ----------------------------------------------------------------

export type SteamTarget =
  // a game/app store page → gets the official click-to-play store widget
  | { kind: 'app'; appId: string }
  // a news/announcement post → a rich card (no official widget for these)
  | { kind: 'news'; appId: string; gid: string }
  // bundle / sub / dlc pages → an OG store card (no per-app widget)
  | { kind: 'store'; label: string };

/**
 * Classify a store.steampowered.com content URL. Only content pages (app / news /
 * bundle / sub / dlc) match; the homepage, search, wishlist, cart etc. return
 * null and fall through to the generic preview card.
 */
export function getSteamTarget(url: string): SteamTarget | null {
  try {
    const { hostname, pathname } = new URL(url);
    if (hostname.replace(/^www\./, '') !== 'store.steampowered.com') return null;
    let m = pathname.match(/^\/news\/app\/(\d+)\/view\/(\d+)/);
    if (m) return { kind: 'news', appId: m[1], gid: m[2] };
    m = pathname.match(/^\/app\/(\d+)/);
    if (m) return { kind: 'app', appId: m[1] };
    m = pathname.match(/^\/(bundle|sub|dlc)\/\d+/);
    if (m) return { kind: 'store', label: m[1] };
    return null;
  } catch {
    return null;
  }
}

/** Steam's official embeddable store widget (live price / discount / Buy). */
export function steamWidgetEmbedUrl(appId: string): string {
  return `https://store.steampowered.com/widget/${encodeURIComponent(appId)}/`;
}

// --- Twitch ---------------------------------------------------------------

export type TwitchTarget =
  | { type: 'channel'; value: string }
  | { type: 'video'; value: string }
  | { type: 'clip'; value: string };

// Twitch's own reserved first-path segments — single-segment paths that are
// utility pages, not channels, and must NOT be embedded as `channel=<x>`.
const TWITCH_RESERVED = new Set([
  'directory',
  'videos',
  'settings',
  'subscriptions',
  'following',
  'followers',
  'friends',
  'inventory',
  'wallet',
  'drops',
  'prime',
  'turbo',
  'downloads',
  'jobs',
  'store',
  'search',
  'dashboard',
  'popout',
  'p',
  'u',
  'team',
]);

export function getTwitchTarget(url: string): TwitchTarget | null {
  try {
    const { hostname, pathname } = new URL(url);
    const h = hostname.replace(/^www\./, '');
    const parts = pathname.replace(/^\/+|\/+$/g, '').split('/');
    if (h === 'clips.twitch.tv' && parts[0]) return { type: 'clip', value: parts[0] };
    if (h === 'twitch.tv' || h === 'm.twitch.tv') {
      if (parts[0] === 'videos' && parts[1]) return { type: 'video', value: parts[1] };
      if (parts[1] === 'clip' && parts[2]) return { type: 'clip', value: parts[2] };
      if (parts.length === 1 && parts[0] && !TWITCH_RESERVED.has(parts[0].toLowerCase())) {
        return { type: 'channel', value: parts[0] };
      }
    }
  } catch {
    /* ignore */
  }
  return null;
}

// --- Spotify --------------------------------------------------------------

const SPOTIFY_TYPES = ['track', 'album', 'playlist', 'episode', 'show', 'artist'] as const;
export type SpotifyType = (typeof SPOTIFY_TYPES)[number];

export function getSpotifyEmbedTarget(url: string): { type: SpotifyType; id: string } | null {
  try {
    const { hostname, pathname } = new URL(url);
    if (hostname !== 'open.spotify.com') return null;
    // /intl-xx/track/<id> or /track/<id>
    const parts = pathname.replace(/^\/+/, '').split('/');
    const idx = parts.findIndex((p) => (SPOTIFY_TYPES as readonly string[]).includes(p));
    if (idx === -1 || !parts[idx + 1]) return null;
    return { type: parts[idx] as SpotifyType, id: parts[idx + 1].split('?')[0] };
  } catch {
    return null;
  }
}

// --- SoundCloud -----------------------------------------------------------

// SoundCloud's own site sections (first segment) that are never `<artist>`.
const SOUNDCLOUD_RESERVED = new Set([
  'discover',
  'you',
  'stream',
  'search',
  'upload',
  'settings',
  'notifications',
  'messages',
  'tags',
  'charts',
  'people',
  'pages',
  'terms',
  'pro',
]);
// Profile tabs — `/<artist>/<tab>` is a listing, not a single track (a real set
// is the deeper `/<artist>/sets/<slug>`, which has length >= 3 and is allowed).
const SOUNDCLOUD_PROFILE_TABS = new Set([
  'tracks',
  'sets',
  'albums',
  'reposts',
  'likes',
  'following',
  'followers',
  'comments',
  'popular-tracks',
  'toptracks',
]);

export function isSoundCloudTrack(url: string): boolean {
  try {
    const { hostname, pathname } = new URL(url);
    // NOTE: on.soundcloud.com short links are NOT handled here — the w.soundcloud
    // widget resolver doesn't follow the redirect; supporting them needs an oEmbed
    // round-trip (soundcloud.com/oembed is CORS-enabled) to get the canonical URL.
    if (hostname.replace(/^www\./, '') !== 'soundcloud.com') return false;
    // /<artist>/<track|sets/set> — at least two segments, not a bare profile
    const parts = pathname
      .replace(/^\/+|\/+$/g, '')
      .split('/')
      .filter(Boolean);
    if (parts.length < 2) return false;
    if (SOUNDCLOUD_RESERVED.has(parts[0].toLowerCase())) return false;
    // `/<artist>/<tab>` profile-tab listing (not a playable single track/set).
    if (parts.length === 2 && SOUNDCLOUD_PROFILE_TABS.has(parts[1].toLowerCase())) return false;
    return true;
  } catch {
    return false;
  }
}

// --- Apple Music ----------------------------------------------------------

/** music.apple.com/<cc>/album|playlist|song/<slug>/<id>[?i=<songId>] → embed player. */
export function getAppleMusicEmbed(
  url: string,
): { embedUrl: string; height: number; video: boolean } | null {
  try {
    const u = new URL(url);
    if (u.hostname !== 'music.apple.com' && u.hostname !== 'embed.music.apple.com') return null;
    if (!/\/(album|playlist|song|music-video)\//.test(u.pathname)) return null;
    const embedUrl = `https://embed.music.apple.com${u.pathname}${u.search}`;
    const video = /\/music-video\//.test(u.pathname);
    // A single song (?i=… on an album, or a /song/ link) is compact; collections are tall.
    const isSong = u.searchParams.has('i') || /\/song\//.test(u.pathname);
    return { embedUrl, height: isSong ? 175 : 450, video };
  } catch {
    return null;
  }
}

// --- X / Twitter ----------------------------------------------------------

export function getTweetId(url: string): string | null {
  try {
    const { hostname, pathname } = new URL(url);
    const h = hostname.replace(/^www\./, '');
    if (h !== 'twitter.com' && h !== 'x.com' && h !== 'mobile.twitter.com') return null;
    const m = pathname.match(/\/status(?:es)?\/(\d+)/);
    return m ? m[1] : null;
  } catch {
    return null;
  }
}

// --- Tidal ----------------------------------------------------------------

export function getTidalEmbed(
  url: string,
): { kind: 'audio' | 'landscape'; embedUrl: string; height?: number } | null {
  try {
    const u = new URL(url);
    const h = u.hostname.replace(/^(www|listen|desktop)\./, '');
    if (h !== 'tidal.com') return null;
    const p = u.pathname.replace(/^\/browse/, '');
    let m = p.match(/^\/track\/(\d+)/);
    if (m)
      return { kind: 'audio', embedUrl: `https://embed.tidal.com/tracks/${m[1]}`, height: 120 };
    m = p.match(/^\/album\/(\d+)/);
    if (m)
      // layout=gridify → full-width grid that fills the container (fixes the
      // narrow/centered default); ~275px is Tidal's own album embed height.
      return {
        kind: 'audio',
        embedUrl: `https://embed.tidal.com/albums/${m[1]}?layout=gridify`,
        height: 275,
      };
    m = p.match(/^\/playlist\/([0-9a-fA-F-]+)/);
    if (m)
      return {
        kind: 'audio',
        embedUrl: `https://embed.tidal.com/playlists/${m[1]}?layout=gridify`,
        height: 275,
      };
    m = p.match(/^\/video\/(\d+)/);
    if (m) return { kind: 'landscape', embedUrl: `https://embed.tidal.com/videos/${m[1]}` };
  } catch {
    /* ignore */
  }
  return null;
}

// --- Instagram ------------------------------------------------------------

export function getInstagramEmbed(url: string): string | null {
  try {
    const u = new URL(url);
    if (u.hostname !== 'instagram.com' && u.hostname !== 'www.instagram.com') return null;
    const m = u.pathname.match(/^\/(p|reel|reels|tv)\/([A-Za-z0-9_-]+)/);
    if (!m) return null;
    const type = m[1] === 'reels' ? 'reel' : m[1];
    return `https://www.instagram.com/${type}/${m[2]}/embed/`;
  } catch {
    return null;
  }
}

// --- Reddit (post embed via embed.reddit.com — reddit's own oEmbed widget host;
//     www.redditmedia.com now 301s here. Bypasses the homeserver's blocked preview
//     fetch, which Reddit serves a bot-check "please wait" page to) ---

const REDDIT_EMBED_QS = '?ref_source=embed&ref=share&embed=true&theme=dark';

export function getRedditPostEmbed(url: string): string | null {
  try {
    const u = new URL(url);
    const h = u.hostname.replace(/^(www|old|new|np|i)\./, '');
    // embed.reddit.com ONLY renders the /r/<sub>/comments/<id> path (verified against
    // reddit's own embed widgets.js). A bare /comments/<id> — all we could build from a
    // redd.it short link or an i./v.redd.it media host — serves a "not found" page, so
    // return null for those and let the caller's og:url fallback resolve the canonical
    // /r/<sub>/comments/<id> URL (from the redirect) before re-parsing into a real embed.
    if (h !== 'reddit.com') return null;
    const m = u.pathname.match(/^\/r\/([A-Za-z0-9_]+)\/comments\/([A-Za-z0-9]+)/);
    if (!m) return null;
    return `https://embed.reddit.com/r/${m[1]}/comments/${m[2]}/${REDDIT_EMBED_QS}`;
  } catch {
    return null;
  }
}

// --- Loom -----------------------------------------------------------------

export function getLoomId(url: string): string | null {
  try {
    const { hostname, pathname } = new URL(url);
    if (hostname.replace(/^www\./, '') !== 'loom.com') return null;
    const m = pathname.match(/^\/(?:share|embed)\/([A-Za-z0-9]+)/);
    return m ? m[1] : null;
  } catch {
    return null;
  }
}

// --- Kick (live channels only; VODs/clips have no clean iframe) ------------

// Kick's own reserved first-path segments (nav pages, not channels).
const KICK_RESERVED = new Set([
  'browse',
  'following',
  'category',
  'categories',
  'search',
  'messages',
  'subscriptions',
  'settings',
  'wallet',
  'help',
  'clips',
  'dashboard',
]);

export function getKickChannel(url: string): string | null {
  try {
    const { hostname, pathname } = new URL(url);
    if (hostname.replace(/^www\./, '') !== 'kick.com') return null;
    const parts = pathname
      .replace(/^\/+|\/+$/g, '')
      .split('/')
      .filter(Boolean);
    return parts.length === 1 &&
      /^[A-Za-z0-9_]+$/.test(parts[0]) &&
      !KICK_RESERVED.has(parts[0].toLowerCase())
      ? parts[0]
      : null;
  } catch {
    return null;
  }
}

// --- Bluesky --------------------------------------------------------------

export function getBlueskyEmbed(url: string): string | null {
  try {
    const { hostname, pathname } = new URL(url);
    if (hostname !== 'bsky.app' && hostname !== 'www.bsky.app') return null;
    const m = pathname.match(/^\/profile\/([^/]+)\/post\/([A-Za-z0-9]+)/);
    // authority is a handle or did; embed.bsky.app resolves either.
    return m ? `https://embed.bsky.app/embed/${m[1]}/app.bsky.feed.post/${m[2]}` : null;
  } catch {
    return null;
  }
}

// --- Mixcloud -------------------------------------------------------------

// Mixcloud's own site sections (first segment) that are never a `<user>`.
const MIXCLOUD_RESERVED = new Set([
  'discover',
  'categories',
  'upload',
  'live',
  'settings',
  'notifications',
  'search',
  'tag',
  'select',
  'browse',
]);
// Profile tabs — `/<user>/<tab>` is a listing, not a single cloudcast.
const MIXCLOUD_PROFILE_TABS = new Set([
  'uploads',
  'favorites',
  'listens',
  'following',
  'followers',
  'playlists',
  'stream',
  'reposts',
]);

/**
 * Canonical Mixcloud cloudcast feed URL (`/<user>/<slug>/`) for the widget's
 * `feed=` param, or null. Bare profiles / profile-tab listings / site sections
 * are excluded (they aren't a single playable cloudcast).
 */
export function getMixcloudFeed(url: string): string | null {
  try {
    const u = new URL(url);
    if (u.hostname.replace(/^www\./, '') !== 'mixcloud.com') return null;
    const parts = u.pathname
      .replace(/^\/+|\/+$/g, '')
      .split('/')
      .filter(Boolean);
    if (parts.length < 2) return null;
    if (MIXCLOUD_RESERVED.has(parts[0].toLowerCase())) return null;
    if (MIXCLOUD_PROFILE_TABS.has(parts[1].toLowerCase())) return null;
    return `https://www.mixcloud.com/${parts[0]}/${parts[1]}/`;
  } catch {
    return null;
  }
}

// --- Deezer ---------------------------------------------------------------

// NB: Deezer podcast pages are `/show/<id>`, not `/podcast/<id>` — the latter
// 404s on their own oEmbed API, and `widget.deezer.com/widget/dark/show/<id>`
// is the matching widget path.
const DEEZER_TYPES = ['track', 'album', 'playlist', 'artist', 'show', 'episode'] as const;
export type DeezerType = (typeof DEEZER_TYPES)[number];

/** deezer.com[/<locale>]/<type>/<id> → widget target, or null. */
export function getDeezerEmbed(url: string): { type: DeezerType; id: string } | null {
  try {
    const u = new URL(url);
    if (u.hostname.replace(/^www\./, '') !== 'deezer.com') return null;
    const parts = u.pathname.replace(/^\/+/, '').split('/').filter(Boolean);
    // optional locale prefix (/en/, /us/, /fr/…) then <type>/<id>
    const idx = parts.findIndex((p) => (DEEZER_TYPES as readonly string[]).includes(p));
    if (idx === -1 || !parts[idx + 1]) return null;
    const id = parts[idx + 1].split('?')[0];
    if (!/^\d+$/.test(id)) return null;
    return { type: parts[idx] as DeezerType, id };
  } catch {
    return null;
  }
}

/**
 * Deezer single track/episode players are compact; collections show a scrollable
 * tracklist and need room. Mirrors the Spotify sizing (152 / 352) — the widget
 * requests `tracklist=true`, so 352 keeps the list from being clipped the way a
 * shorter box would.
 */
export function deezerEmbedHeight(type: DeezerType): number {
  return type === 'track' || type === 'episode' ? 152 : 352;
}

// --- Embed-URL builders ---------------------------------------------------

const enc = encodeURIComponent;

export function buildVideoEmbedUrl(
  provider: 'youtube' | 'vimeo',
  id: string,
  hash?: string,
): string {
  if (provider === 'vimeo') {
    // dnt=1 = Do Not Track (no non-essential cookies); h={hash} required for unlisted.
    return `https://player.vimeo.com/video/${enc(id)}?autoplay=1&dnt=1${
      hash ? `&h=${enc(hash)}` : ''
    }`;
  }
  // playsinline=1 keeps iOS Safari from forcing the native fullscreen player.
  return `https://www.youtube-nocookie.com/embed/${enc(id)}?autoplay=1&rel=0&playsinline=1`;
}

/** Spotify compact players (track/episode) are short; collections are taller. */
export function spotifyEmbedHeight(type: SpotifyType): number {
  return type === 'track' || type === 'episode' ? 152 : 352;
}

/**
 * Resolve any supported media URL to an embed spec, or null if none applies.
 * `host` is the current page hostname — required by Twitch's `parent` param.
 */
export function parseMediaEmbed(url: string, host: string): MediaEmbed | null {
  const shortsId = getYoutubeShortsId(url);
  if (shortsId)
    return {
      provider: 'youtube',
      kind: 'portrait',
      embedUrl: buildVideoEmbedUrl('youtube', shortsId),
    };

  const ytId = getYouTubeVideoId(url);
  if (ytId)
    return {
      provider: 'youtube',
      kind: 'landscape',
      embedUrl: buildVideoEmbedUrl('youtube', ytId),
    };

  const vimeo = getVimeoParts(url);
  if (vimeo)
    return {
      provider: 'vimeo',
      kind: 'landscape',
      embedUrl: buildVideoEmbedUrl('vimeo', vimeo.id, vimeo.hash),
    };

  // NOTE: TikTok is handled by its own card (TikTokEmbedCard) — short "copy-link"
  // URLs (vm.tiktok.com, tiktok.com/t/…) need a client-side oEmbed lookup to
  // resolve the video id, which a sync parser can't do. See isTikTokLink below.

  const dmId = getDailymotionId(url);
  if (dmId)
    return {
      provider: 'dailymotion',
      kind: 'landscape',
      // geo.dailymotion.com is the current player; the old /embed/video path was
      // deprecated in Sept 2024.
      embedUrl: `https://geo.dailymotion.com/player.html?video=${enc(dmId)}&autoplay=1`,
    };

  const streamableId = getStreamableId(url);
  if (streamableId)
    return {
      provider: 'streamable',
      kind: 'landscape',
      embedUrl: `https://streamable.com/e/${enc(streamableId)}?autoplay=1`,
    };

  const twitch = getTwitchTarget(url);
  if (twitch) {
    const parent = enc(host);
    const embedUrl =
      twitch.type === 'clip'
        ? `https://clips.twitch.tv/embed?clip=${enc(twitch.value)}&parent=${parent}&autoplay=true`
        : `https://player.twitch.tv/?${twitch.type}=${enc(twitch.value)}&parent=${parent}&autoplay=true`;
    return { provider: 'twitch', kind: 'landscape', embedUrl };
  }

  const spotify = getSpotifyEmbedTarget(url);
  if (spotify)
    return {
      provider: 'spotify',
      kind: 'audio',
      embedUrl: `https://open.spotify.com/embed/${spotify.type}/${enc(spotify.id)}`,
      height: spotifyEmbedHeight(spotify.type),
    };

  if (isSoundCloudTrack(url))
    return {
      provider: 'soundcloud',
      kind: 'audio',
      embedUrl: `https://w.soundcloud.com/player/?url=${enc(
        url,
      )}&color=%23ff5500&auto_play=true&show_comments=false&hide_related=true`,
      height: 166,
    };

  const apple = getAppleMusicEmbed(url);
  if (apple)
    return {
      provider: 'applemusic',
      kind: apple.video ? 'landscape' : 'audio',
      embedUrl: apple.embedUrl,
      height: apple.video ? undefined : apple.height,
    };

  const tidal = getTidalEmbed(url);
  if (tidal)
    return { provider: 'tidal', kind: tidal.kind, embedUrl: tidal.embedUrl, height: tidal.height };

  const mixFeed = getMixcloudFeed(url);
  if (mixFeed)
    return {
      provider: 'mixcloud',
      kind: 'audio',
      embedUrl: `https://www.mixcloud.com/widget/iframe/?feed=${enc(mixFeed)}&light=0`,
      height: 120,
    };

  const deezer = getDeezerEmbed(url);
  if (deezer)
    return {
      provider: 'deezer',
      kind: 'audio',
      embedUrl: `https://widget.deezer.com/widget/dark/${deezer.type}/${enc(
        deezer.id,
      )}?app_id=457142&autoplay=false&radius=true&tracklist=true`,
      height: deezerEmbedHeight(deezer.type),
    };

  const insta = getInstagramEmbed(url);
  if (insta) return { provider: 'instagram', kind: 'rich', embedUrl: insta, height: 720 };

  const reddit = getRedditPostEmbed(url);
  if (reddit) return { provider: 'reddit', kind: 'rich', embedUrl: reddit, height: 480 };

  const bsky = getBlueskyEmbed(url);
  if (bsky) return { provider: 'bluesky', kind: 'rich', embedUrl: bsky, height: 600 };

  const loomId = getLoomId(url);
  if (loomId)
    return {
      provider: 'loom',
      kind: 'landscape',
      embedUrl: `https://www.loom.com/embed/${enc(loomId)}`,
    };

  const kick = getKickChannel(url);
  if (kick)
    return {
      provider: 'kick',
      kind: 'landscape',
      embedUrl: `https://player.kick.com/${enc(kick)}?autoplay=true`,
    };

  return null;
}
