// ---------------------------------------------------------------------------
// Nitter RSS feed fetcher & parser
// Uses regex-based XML extraction — no external XML library required.
// ---------------------------------------------------------------------------

export type NitterRssItem = {
  id: string;
  username: string;
  title: string;
  content: string;
  link: string;
  pubDate: string;
  pubDateMs: number;
  isRetweet: boolean;
  isReply: boolean;
  mediaUrls: string[];
};

export type NitterRssFetchResult =
  | { ok: true; items: NitterRssItem[] }
  | { ok: false; error: string; statusCode?: number };

// ---------------------------------------------------------------------------
// HTML helpers
// ---------------------------------------------------------------------------

const ENTITY_MAP: Record<string, string> = {
  "&amp;": "&",
  "&lt;": "<",
  "&gt;": ">",
  "&quot;": '"',
  "&#39;": "'",
  "&#x27;": "'",
  "&#x2F;": "/",
  "&apos;": "'",
};

const ENTITY_RE = /&(?:amp|lt|gt|quot|apos|#39|#x27|#x2F);/g;

export function stripHtmlTags(html: string): string {
  // Remove HTML tags, then decode basic entities
  const stripped = html.replace(/<[^>]*>/g, "");
  return stripped.replace(ENTITY_RE, (m) => ENTITY_MAP[m] ?? m).trim();
}

export function extractMediaFromHtml(html: string): string[] {
  const urls: string[] = [];
  // Match img src and video src attributes
  const srcRe = /<(?:img|video)[^>]+\bsrc\s*=\s*["']([^"']+)["']/gi;
  let match: RegExpExecArray | null;
  while ((match = srcRe.exec(html)) !== null) {
    if (match[1]) urls.push(match[1]);
  }
  return urls;
}

// ---------------------------------------------------------------------------
// XML helpers (regex-based, handles CDATA)
// ---------------------------------------------------------------------------

function unwrapCdata(raw: string): string {
  return raw.replace(/<!\[CDATA\[([\s\S]*?)]]>/g, "$1").trim();
}

function extractTag(xml: string, tag: string): string {
  const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}>`, "i");
  const m = xml.match(re);
  return m ? unwrapCdata(m[1]) : "";
}

// ---------------------------------------------------------------------------
// Core fetch + parse
// ---------------------------------------------------------------------------

export type FetchNitterRssOptions = {
  instanceUrl: string;
  username: string;
  maxItems?: number;
  timeoutMs?: number;
};

function parseItems(xml: string, username: string, maxItems: number): NitterRssItem[] {
  const items: NitterRssItem[] = [];
  const itemRe = /<item>([\s\S]*?)<\/item>/gi;
  let itemMatch: RegExpExecArray | null;

  while ((itemMatch = itemRe.exec(xml)) !== null) {
    if (items.length >= maxItems) break;

    const block = itemMatch[1];
    const title = stripHtmlTags(extractTag(block, "title"));
    const link = stripHtmlTags(extractTag(block, "link"));
    const guid = stripHtmlTags(extractTag(block, "guid"));
    const rawDescription = extractTag(block, "description");
    const content = stripHtmlTags(rawDescription);
    const mediaUrls = extractMediaFromHtml(rawDescription);
    const pubDateRaw = stripHtmlTags(extractTag(block, "pubDate"));

    const pubDateMs = pubDateRaw ? new Date(pubDateRaw).getTime() : 0;
    const pubDate = pubDateMs ? new Date(pubDateMs).toISOString() : "";

    const isRetweet = title.startsWith("RT by") || content.startsWith("RT @");
    const isReply = content.startsWith("@") || (link.includes("/status/") && link.includes("#m"));

    items.push({
      id: guid || link,
      username,
      title,
      content,
      link,
      pubDate,
      pubDateMs,
      isRetweet,
      isReply,
      mediaUrls,
    });
  }

  return items;
}

export async function fetchNitterRss(opts: FetchNitterRssOptions): Promise<NitterRssFetchResult> {
  const { instanceUrl, username, maxItems = 20, timeoutMs = 15_000 } = opts;
  const url = `${instanceUrl.replace(/\/+$/, "")}/${username.replace(/^@/, "")}/rss`;

  let res: Response;
  try {
    res = await globalThis.fetch(url, {
      signal: AbortSignal.timeout(timeoutMs),
      headers: { Accept: "application/rss+xml, application/xml, text/xml" },
    });
  } catch (err: unknown) {
    const msg = err instanceof Error ? err.message : String(err);
    return { ok: false, error: `Fetch failed: ${msg}` };
  }

  if (!res.ok) {
    return {
      ok: false,
      error: `HTTP ${res.status} ${res.statusText}`,
      statusCode: res.status,
    };
  }

  let xml: string;
  try {
    xml = await res.text();
  } catch (err: unknown) {
    const msg = err instanceof Error ? err.message : String(err);
    return { ok: false, error: `Failed to read response body: ${msg}` };
  }

  const items = parseItems(xml, username.replace(/^@/, ""), maxItems);
  return { ok: true, items };
}
