function withTimeout(promise, ms, label) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
    promise.then(
      (v) => {
        clearTimeout(timer);
        resolve(v);
      },
      (e) => {
        clearTimeout(timer);
        reject(e);
      }
    );
  });
}

function toStreamArray(raw, providerName) {
  let arr = Array.isArray(raw) ? raw : raw && typeof raw === 'object' && raw.url ? [raw] : [];
  return arr
    .filter((s) => s && typeof s === 'object' && typeof s.url === 'string' && s.url.length > 0)
    .map((s) => ({ ...s, _provider: s._provider || providerName }));
}

async function scrapeAll(providers, media, timeoutMs) {
  const settled = await Promise.allSettled(
    providers.map(async (p) => {
      const raw = await withTimeout(
        Promise.resolve().then(() =>
          p.getStreams(String(media.id), media.type, media.seasonId ?? null, media.episodeId ?? null)
        ),
        timeoutMs,
        p.name
      );
      return toStreamArray(raw, p.name);
    })
  );

  const streams = [];
  const errors = {};
  settled.forEach((r, i) => {
    if (r.status === 'fulfilled') streams.push(...r.value);
    else errors[providers[i].name] = (r.reason && r.reason.message) || String(r.reason);
  });

  const seen = new Set();
  const unique = [];
  for (const s of streams) {
    const key = `${s._provider}|${s.url}|${s.quality || ''}|${s.name || s.title || ''}`;
    if (seen.has(key)) continue;
    seen.add(key);
    unique.push(s);
  }
  return { streams: unique, errors };
}

module.exports = { scrapeAll, withTimeout };
