/**
 * Parse an arbitrary video URL into a structured embed descriptor. Supports
 * YouTube (watch/short/embed variants), Vimeo, and direct MP4/WEBM links.
 * Used by both the admin editor (when inserting a video) and the public
 * renderer (when transforming shortcodes into iframes/<video>).
 */
export type VideoEmbed =
  | { provider: "youtube"; id: string; src: string; embed: string }
  | { provider: "vimeo"; id: string; src: string; embed: string }
  | { provider: "mp4"; id: string; src: string; embed: string };

const YT_HOSTS = new Set([
  "youtube.com",
  "www.youtube.com",
  "m.youtube.com",
  "youtu.be",
  "www.youtu.be",
]);
const VIMEO_HOSTS = new Set(["vimeo.com", "www.vimeo.com", "player.vimeo.com"]);

/**
 * Allow-list for DOMPurify — any iframe whose src starts with one of these
 * prefixes is preserved through sanitisation. Keep in sync with the
 * `embed` URLs generated below.
 */
export const VIDEO_IFRAME_PREFIXES = [
  "https://www.youtube.com/embed/",
  "https://www.youtube-nocookie.com/embed/",
  "https://player.vimeo.com/video/",
];

export function parseVideoUrl(rawUrl: string): VideoEmbed | null {
  const url = (rawUrl || "").trim();
  if (!url) return null;
  // Direct MP4/WEBM URL.
  if (/\.(mp4|webm)(\?.*)?$/i.test(url)) {
    return { provider: "mp4", id: url, src: url, embed: url };
  }

  let u: URL;
  try {
    u = new URL(url);
  } catch {
    return null;
  }

  if (YT_HOSTS.has(u.hostname)) {
    const id =
      u.hostname === "youtu.be" || u.hostname === "www.youtu.be"
        ? u.pathname.replace(/^\/+/, "").split("/")[0]
        : u.searchParams.get("v") ?? u.pathname.split("/").pop() ?? "";
    if (!id || !/^[\w-]{6,}$/.test(id)) return null;
    const embed = `https://www.youtube-nocookie.com/embed/${id}`;
    return { provider: "youtube", id, src: url, embed };
  }

  if (VIMEO_HOSTS.has(u.hostname)) {
    const id = u.pathname.replace(/^\/+/, "").split("/")[0];
    if (!id || !/^\d+$/.test(id)) return null;
    const embed = `https://player.vimeo.com/video/${id}`;
    return { provider: "vimeo", id, src: url, embed };
  }

  return null;
}

/**
 * Render an embed as the HTML shortcode we persist in blog content. Using a
 * shortcode rather than an iframe directly keeps the source markup readable
 * in the DB and in the admin editor.
 */
export function videoShortcode(
  url: string,
  caption?: string,
): string {
  const safeUrl = url.replace(/"/g, "&quot;");
  const safeCaption = (caption ?? "").replace(/"/g, "&quot;");
  return `\n\n[[video url="${safeUrl}"${safeCaption ? ` caption="${safeCaption}"` : ""}]]\n\n`;
}

const SHORTCODE_RE =
  /\[\[video\s+url="([^"]+)"(?:\s+caption="([^"]*)")?\s*\]\]/g;

/**
 * Replace every `[[video url="..." caption="..."]]` shortcode in `content`
 * with a `<figure>` wrapping either an iframe (YouTube/Vimeo) or a native
 * `<video>` element. Returns the transformed content.
 */
export function renderVideoShortcodes(content: string): string {
  return content.replace(SHORTCODE_RE, (_match, rawUrl: string, rawCaption: string | undefined) => {
    const embed = parseVideoUrl(rawUrl);
    if (!embed) return "";
    const caption = (rawCaption ?? "").replace(/&quot;/g, '"');
    const captionHtml = caption
      ? `<figcaption>${escapeHtml(caption)}</figcaption>`
      : "";
    if (embed.provider === "mp4") {
      return `<figure class="video-embed"><video controls preload="metadata" src="${escapeAttr(embed.src)}"></video>${captionHtml}</figure>`;
    }
    const title =
      embed.provider === "youtube" ? "YouTube video" : "Vimeo video";
    return `<figure class="video-embed"><div class="video-embed-frame"><iframe src="${escapeAttr(embed.embed)}" title="${title}" loading="lazy" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>${captionHtml}</figure>`;
  });
}

function escapeHtml(s: string): string {
  return s
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

function escapeAttr(s: string): string {
  return escapeHtml(s);
}
