(() => {
  const DIR = "hiro-play-offline";
  const META_KEY = "hiroOfflineDownloads";
  const directExtension = (type, url) => {
    const clean = String(url || "").split(/[?#]/)[0];
    const match = clean.match(/\.([a-z0-9]{2,5})$/i);
    if (match && ["mp4", "webm", "ogg", "m4v"].includes(match[1].toLowerCase())) return match[1].toLowerCase();
    if (/webm/i.test(type || "")) return "webm";
    if (/ogg/i.test(type || "")) return "ogg";
    return "mp4";
  };
  const root = () => navigator.storage.getDirectory();
  const directory = async (create = true) => (await root()).getDirectoryHandle(DIR, { create });
  const list = async () => (await chrome.storage.local.get({ [META_KEY]: [] }))[META_KEY] || [];
  const saveList = async (items) => chrome.storage.local.set({ [META_KEY]: items.slice(0, 200) });
  const safeTitle = (value) => String(value || "Contenido").replace(/[\\/:*?"<>|]+/g, " ").replace(/\s+/g, " ").trim().slice(0, 100) || "Contenido";
  async function download(url, meta = {}, progress = () => {}) {
    if (!/^https?:/i.test(String(url || ""))) throw new Error("Esta fuente no se puede guardar localmente.");
    if (/\.m3u8(?:[?#]|$)/i.test(url)) throw new Error("Esta reproducción usa transmisión por segmentos. La descarga sin conexión está disponible cuando HIRO PLAY obtiene un archivo de video directo.");
    if (!navigator.storage?.getDirectory) throw new Error("Tu versión de Chrome no admite almacenamiento local para esta función.");
    const response = await fetch(url, { credentials: "include", cache: "no-store" });
    if (!response.ok || !response.body) throw new Error(`No se pudo iniciar la descarga (${response.status || "sin respuesta"}).`);
    const type = response.headers.get("content-type") || "video/mp4";
    if (/text\/html|application\/json/i.test(type)) throw new Error("La fuente requiere autorización del reproductor original y no puede guardarse directamente.");
    const total = Number(response.headers.get("content-length")) || 0;
    const ext = directExtension(type, url);
    const id = crypto.randomUUID();
    const fileName = `${id}.${ext}`;
    const dir = await directory(true);
    const handle = await dir.getFileHandle(fileName, { create: true });
    const writable = await handle.createWritable();
    const reader = response.body.getReader();
    let received = 0;
    try {
      while (true) {
        const { value, done } = await reader.read();
        if (done) break;
        await writable.write(value);
        received += value.byteLength;
        progress({ received, total, percent: total ? Math.min(100, received / total * 100) : 0 });
      }
      await writable.close();
    } catch (error) {
      try { await writable.abort(); } catch {}
      try { await dir.removeEntry(fileName); } catch {}
      throw error;
    }
    const entry = {
      id,
      fileName,
      title: safeTitle(meta.title),
      image: String(meta.image || ""),
      postUrl: String(meta.postUrl || ""),
      sourceUrl: String(url),
      contentType: type,
      size: received || total,
      duration: Number(meta.duration) || 0,
      downloadedAt: Date.now(),
    };
    const items = await list();
    await saveList([entry, ...items.filter((item) => item.id !== id)]);
    return entry;
  }
  async function getFile(entry) {
    const dir = await directory(false);
    const handle = await dir.getFileHandle(entry.fileName, { create: false });
    return handle.getFile();
  }
  async function remove(entry) {
    try { const dir = await directory(false); await dir.removeEntry(entry.fileName); } catch {}
    const items = await list();
    await saveList(items.filter((item) => item.id !== entry.id));
  }
  async function usage() {
    try { return await navigator.storage.estimate(); } catch { return { usage: 0, quota: 0 }; }
  }
  window.HiroOffline = { download, list, getFile, remove, usage };
})();
