// Minimal RSS/Atom fetcher and mapper to unified structure

export async function fetchRssByUrl(feedUrl) {
  const url = ensureString(feedUrl);
  if (!/^https?:\/\//i.test(url)) {
    throw new Error('Invalid RSS feed url');
  }
  const res = await fetch(url, { headers: { Accept: 'application/rss+xml, application/xml, text/xml;q=0.9, */*;q=0.8' } });
  if (!res.ok) {
    throw new Error(`RSS request failed with status ${res.status}`);
  }
  const text = await res.text();
  const nowIso = new Date().toISOString();

  const channelBlock = matchFirst(text, /<channel[\s\S]*?<\/channel>/i) || text; // RSS or Atom root
  const podcastTitle = decodeXml(matchFirst(channelBlock, /<title[\s\S]*?>([\s\S]*?)<\/title>/i) || '') || url;
  const podcastLink = extractLink(channelBlock) || url;
  const podcastCover =
    matchAttr(channelBlock, /<itunes:image[^>]*href="([^"]+)"/i) ||
    decodeXml(matchFirst(channelBlock, /<image[\s\S]*?<url[\s\S]*?>([\s\S]*?)<\/url>[\s\S]*?<\/image>/i) || '');

  const items = [];
  const itemBlocks = matchAll(text, /<item[\s\S]*?<\/item>/gi);
  const entryBlocks = matchAll(text, /<entry[\s\S]*?<\/entry>/gi);
  const blocks = itemBlocks.length ? itemBlocks : entryBlocks;
  blocks.forEach(block => {
    const id = decodeXml(
      matchFirst(block, /<guid[\s\S]*?>([\s\S]*?)<\/guid>/i) ||
      matchFirst(block, /<id[\s\S]*?>([\s\S]*?)<\/id>/i) ||
      extractLink(block) || ''
    );
    if (!id) return;
    const title = decodeXml(matchFirst(block, /<title[\s\S]*?>([\s\S]*?)<\/title>/i) || id);
    const description =
      decodeXml(matchFirst(block, /<description[\s\S]*?>([\s\S]*?)<\/description>/i) || '') ||
      decodeXml(matchFirst(block, /<content:encoded[\s\S]*?>([\s\S]*?)<\/content:encoded>/i) || '') ||
      decodeXml(matchFirst(block, /<summary[\s\S]*?>([\s\S]*?)<\/summary>/i) || '');
    const audioUrl =
      matchAttr(block, /<enclosure[^>]*type="audio\/[^"]+"[^>]*url="([^"]+)"/i) ||
      matchAttr(block, /<enclosure[^>]*url="([^"]+)"[^>]*type="audio\/[^"]+"/i) ||
      matchAttr(block, /<media:content[^>]*url="([^"]+)"/i) || '';
    const publishedAt =
      decodeXml(matchFirst(block, /<pubDate[\s\S]*?>([\s\S]*?)<\/pubDate>/i) || '') ||
      decodeXml(matchFirst(block, /<updated[\s\S]*?>([\s\S]*?)<\/updated>/i) || '') ||
      decodeXml(matchFirst(block, /<published[\s\S]*?>([\s\S]*?)<\/published>/i) || '') || null;
    const durationText = matchFirst(block, /<itunes:duration[\s\S]*?>([\s\S]*?)<\/itunes:duration>/i) || '';
    const duration = parseItunesDuration(durationText);
    const link = extractLink(block) || '';
    const itemCover = matchAttr(block, /<itunes:image[^>]*href="([^"]+)"/i) || '';

    items.push({
      id,
      title,
      description,
      audioUrl,
      publishedAt,
      duration,
      link,
      image: itemCover,
    });
  });

  const mappedEpisodes = items.map((it, index) => ({
    id: ensureString(it.id || `${url}:${index}`),
    // 统一字段命名，兼容 Options/Popup 渲染
    episodeTitle: ensureString(it.title || ''),
    episodeDescription: ensureString(it.description || ''),
    audioUrl: ensureString(it.audioUrl || ''),
    publishedAt: it.publishedAt || null,
    duration: it.duration || undefined,
    episodeLink: ensureString(it.link || ''),
    // 节目封面统一使用播客封面
    cover: ensureString(podcastCover || ''),
    podcastTitle: podcastTitle,
    podcastSourceRef: url,
    podcastLink: podcastLink,
    source: 'rss',
  }));

  return {
    data: {
      podcast: {
        title: podcastTitle,
        link: podcastLink,
        cover: podcastCover || undefined,
      },
      episodes: mappedEpisodes,
    },
    meta: {
      source: 'rss',
      sourceRef: url,
      fetchedAt: nowIso,
      cache: { status: 'updated', cachedAt: nowIso },
    },
  };
}

function matchFirst(text, regex) {
  const m = regex.exec(text);
  return m ? m[1] : '';
}

function matchAll(text, regex) {
  const out = [];
  let m;
  while ((m = regex.exec(text))) {
    out.push(m[0]);
  }
  return out;
}

function matchAttr(text, regex) {
  const m = regex.exec(text);
  return m ? m[1] : '';
}

function decodeXml(str) {
  const s = String(str || '');
  return s
    .replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1')
    .replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .replace(/&amp;/g, '&')
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'");
}

function extractLink(block) {
  const href = matchAttr(block, /<link[^>]*href="([^"]+)"/i);
  if (href) return href;
  return decodeXml(matchFirst(block, /<link[\s\S]*?>([\s\S]*?)<\/link>/i) || '');
}

function parseItunesDuration(text) {
  const t = (text || '').trim();
  if (!t) return undefined;
  const parts = t.split(':').map(n => Number(n));
  if (parts.some(n => !Number.isFinite(n))) return undefined;
  if (parts.length === 3) {
    return parts[0] * 3600 + parts[1] * 60 + parts[2];
  }
  if (parts.length === 2) {
    return parts[0] * 60 + parts[1];
  }
  if (parts.length === 1) {
    return parts[0];
  }
  return undefined;
}

function ensureString(v) {
  if (typeof v === 'string') return v.trim();
  if (typeof v === 'number') return String(v);
  return '';
}
