/**
 * Letterboxd RSS reader. Letterboxd publishes an RSS endpoint for any
 * public list at `/{user}/list/{slug}/rss/` containing per-film entries
 * with embedded TMDB ids. We can't reach it directly because Letterboxd
 * doesn't set CORS headers, so requests route through one of several
 * open proxies that add Access-Control-Allow-Origin headers.
 *
 * Multiple proxies are tried in sequence so one provider going down
 * doesn't break the feature outright.
 */

const PROXIES: Array<(target: string) => string> = [
  target => `https://api.allorigins.win/raw?url=${encodeURIComponent(target)}`,
  target => `https://corsproxy.io/?${encodeURIComponent(target)}`,
  target => `https://api.codetabs.com/v1/proxy?quest=${encodeURIComponent(target)}`,
]

export interface LetterboxdListItem {
  /** TMDB id parsed from the entry, when available. Letterboxd publishes
   *  this in a `<tmdb:movieId>` element on most film entries. */
  tmdbId: string | null
  /** Letterboxd film url, used as a fallback link. */
  link: string
  title: string
  year: number | null
  /** Order within the user's list - 1-based. */
  position: number
}

export interface LetterboxdList {
  title: string
  description: string
  link: string
  items: LetterboxdListItem[]
}

/**
 * Validate + normalise a user-pasted Letterboxd list URL into the
 * canonical `/{user}/list/{slug}/` form. Returns null when the URL
 * doesn't look like a list.
 */
export function normaliseLetterboxdListUrl(raw: string): string | null {
  const trimmed = raw.trim()
  if (!trimmed) return null
  let parsed: URL
  try {
    parsed = new URL(trimmed)
  } catch {
    return null
  }
  if (!parsed.hostname.endsWith('letterboxd.com')) return null
  // Path looks like /{user}/list/{slug}/[detail/[page/N/]]?
  const m = parsed.pathname.match(/^\/([^/]+)\/list\/([^/]+)/)
  if (!m) return null
  return `https://letterboxd.com/${m[1]}/list/${m[2]}/`
}

function listRssUrl(canonicalUrl: string): string {
  return canonicalUrl.replace(/\/?$/, '/rss/')
}

/**
 * Fetch + parse a Letterboxd list's RSS through the allorigins proxy.
 * Robust against missing TMDB ids - entries without one carry a null
 * `tmdbId` and the renderer can fall back to title-only display.
 */
export async function fetchLetterboxdList(canonicalUrl: string): Promise<LetterboxdList | null> {
  const url = listRssUrl(canonicalUrl)
  let xml: string | null = null
  for (const buildUrl of PROXIES) {
    try {
      const res = await fetch(buildUrl(url))
      if (!res.ok) continue
      const body = await res.text()
      if (body && body.trim().startsWith('<')) {
        xml = body
        break
      }
    } catch {
      // Try the next proxy.
    }
  }
  if (!xml) return null

  let doc: Document
  try {
    doc = new DOMParser().parseFromString(xml, 'application/xml')
  } catch {
    return null
  }
  // DOMParser returns an error doc when the XML is malformed; check for
  // the parsererror node before trusting the result.
  if (doc.querySelector('parsererror')) return null

  const channelTitle = doc.querySelector('channel > title')?.textContent || 'Letterboxd list'
  const channelDesc = doc.querySelector('channel > description')?.textContent || ''
  const channelLink = doc.querySelector('channel > link')?.textContent || canonicalUrl

  const items: LetterboxdListItem[] = []
  let pos = 0
  for (const entry of Array.from(doc.querySelectorAll('channel > item'))) {
    pos++
    const titleRaw = entry.querySelector('title')?.textContent || ''
    const link = entry.querySelector('link')?.textContent || ''
    // Various Letterboxd RSS variants. tmdb:movieId is the most common.
    const tmdbEl =
      entry.getElementsByTagName('tmdb:movieId')[0] ||
      entry.getElementsByTagName('letterboxd:tmdbId')[0] ||
      null
    const yearEl =
      entry.getElementsByTagName('letterboxd:filmYear')[0] ||
      null
    const titleElLb = entry.getElementsByTagName('letterboxd:filmTitle')[0]
    const cleanTitle = (titleElLb?.textContent || titleRaw)
      // Letterboxd titles often look like "Title, 2007 - ★★★★½" - strip rating + year suffix.
      .replace(/\s*-\s*★+½?\s*$/u, '')
      .replace(/,\s*\d{4}\s*$/, '')
      .trim()
    const yearFromTitle = (titleRaw.match(/,\s*(\d{4})\s*-/) || [])[1]
    items.push({
      tmdbId: tmdbEl?.textContent?.trim() || null,
      link,
      title: cleanTitle || titleRaw,
      year: yearEl?.textContent ? Number(yearEl.textContent) : yearFromTitle ? Number(yearFromTitle) : null,
      position: pos,
    })
  }

  return {
    title: channelTitle.replace(/^Letterboxd \| /, ''),
    description: channelDesc,
    link: channelLink,
    items,
  }
}
