import { anilistClient } from '@/modules/anilist.js'
import { settings } from '@/modules/settings.js'
import { findEdge, resolveSeason, getMediaMaxEp, mapBestRelease } from '../anime.js'
import { exclusions, getRSSContent, parseRSSNodes } from '../rss.js'

export default async function getRSSEntries ({ media, episode, mode, ignoreQuality }) {
  // mode cuts down on the amt of queries made 'check' || 'batch'
  const titles = createTitle(media).join(')|(')

  const prequel = findEdge(media, 'PREQUEL')?.node
  const sequel = findEdge(media, 'SEQUEL')?.node
  const isBatch = media.status === 'FINISHED' && media.episodes !== 1

  // if media has multiple seasons, and this S is > 1, then get the absolute episode number of the episode
  const absolute = prequel && !mode && (await resolveSeason({ media, episode, force: true }))
  const absoluteep = absolute?.offset + episode
  const episodes = [episode]

  // only use absolute episode number if its smaller than max episodes this series has, ex:
  // looking for E1 of S2, S1 has 12 ep and S2 has 13, absolute will be 13
  // so this would find the 13th ep of the 2nd season too if this check wasnt here
  if (absolute && absoluteep < (getMediaMaxEp(media) || episode)) {
    episodes.push(absoluteep)
  }

  let ep = ''
  if (media.episodes !== 1 && mode !== 'batch') {
    if (isBatch) {
      const digits = Math.max(2, Math.log(media.episodes) * Math.LOG10E + 1 | 0)
      ep = `"${zeropad(1, digits)}-${zeropad(media.episodes, digits)}"|"${zeropad(1, digits)}~${zeropad(media.episodes, digits)}"|"Batch"|"Complete"|"${zeropad(episode)}+"|"${zeropad(episode)}v"`
    } else {
      ep = `(${episodes.map(epstring).join('|')})`
    }
  }

  const excl = exclusions.join('|')
  const quality = (!ignoreQuality && (`"${settings.value.rssQuality}"` || '"1080"')) || ''
  const url = new URL(`${settings.value.catURL}/?page=rss&c=1_2&f=0&s=seeders&o=desc&q=(${titles})${ep}${quality}-(${excl})`)

  let nodes = [...(await getRSSContent(url)).querySelectorAll('item')]

  if (absolute) {
    // if this is S > 1 aka absolute ep number exists get entries for S1title + absoluteEP
    // the reason this isnt done with recursion like sequelEntries is because that would include the S1 media dates
    // we want the dates of the target media as the S1 title might be used for SX releases
    const titles = createTitle(absolute.media).join(')|(')

    const url = new URL(`${settings.value.catURL}/?page=rss&c=1_2&f=0&s=seeders&o=desc&q=(${titles})${epstring(absoluteep)}${quality}-(${excl})`)
    nodes = [...nodes, ...(await getRSSContent(url)).querySelectorAll('item')]
  }

  let entries = parseRSSNodes(nodes)

  const checkSequelDate = media.status === 'FINISHED' && (sequel?.status === 'FINISHED' || sequel?.status === 'RELEASING') && sequel.startDate

  const sequelStartDate = checkSequelDate && new Date(Object.values(checkSequelDate).join(' '))

  // recursive, get all entries for media sequel, and its sequel, and its sequel
  const sequelEntries =
      (sequel?.status === 'FINISHED' || sequel?.status === 'RELEASING') &&
        (await getRSSEntries({ media: (await anilistClient.searchIDSingle({ id: sequel.id })).data.Media, episode, mode: mode || 'check' }))

  const checkPrequelDate = (media.status === 'FINISHED' || media.status === 'RELEASING') && prequel?.status === 'FINISHED' && prequel?.endDate

  const prequelEndDate = checkPrequelDate && new Date(Object.values(checkPrequelDate).join(' '))

  // 1 month in MS, a bit of jitter for pre-releases and releasers being late as fuck, lets hope it doesnt cause issues
  const month = 2674848460

  if (prequelEndDate) {
    entries = entries.filter(entry => entry.date > new Date(+prequelEndDate + month))
  }

  if (sequelStartDate && media.format === 'TV') {
    entries = entries.filter(entry => entry.date < new Date(+sequelStartDate - month))
  }

  if (sequelEntries?.length) {
    if (mode === 'check') {
      entries = [...entries, ...sequelEntries]
    } else {
      entries = entries.filter(entry => !sequelEntries.find(sequel => sequel.link === entry.link))
    }
  }

  // this gets entries without any episode limiting, and for batches
  const batchEntries = !mode && isBatch && (await getRSSEntries({ media, episode, ignoreQuality, mode: 'batch' })).filter(entry => {
    return !epNumRx.test(entry.title)
  })

  if (batchEntries?.length) {
    entries = [...entries, ...batchEntries]
  }

  // some archaic shows only have shit DVD's in weird qualities, so try to look up without any quality restrictions when there are no results
  if (!entries.length && !ignoreQuality && !mode) {
    entries = await getRSSEntries({ media, episode, ignoreQuality: true })
  }

  // dedupe
  const ids = entries.map(e => e.link)
  return mapBestRelease(entries.filter(({ link }, index) => !ids.includes(link, index + 1)))
}

// padleft a variable with 0 ex: 1 => '01'
function zeropad (v = 1, l = 2) {
  return (typeof v === 'string' ? v : v.toString()).padStart(l, '0')
}

const epstring = ep => `"E${zeropad(ep)}+"|"E${zeropad(ep)}v"|"+${zeropad(ep)}+"|"+${zeropad(ep)}v"`
// [EO]?[-EPD _—]\d{2}(?:[-v _.—]|$)
// /[EO]?[-EPD]\d{2}(?:[-v.]|$)|[EO]?[EPD ]\d{2}(?:[v .]|$)|[EO]?[EPD_]\d{2}(?:[v_.]|$)|[EO]?[EPD—]\d{2}(?:[v.—]|$)|\d{2} ?[-~—] ?\d{2}/i
// matches: OP01 ED01 EP01 E01 01v 01. -01- _01_ with spaces and stuff
const epNumRx = /[EO]?[-EPD]\d{2}(?:[-v.]|$)|[EO]?[EPD ]\d{2}(?:[v .]|$)|[EO]?[EPD_]\d{2}(?:[v_.]|$)|[EO]?[EPD—]\d{2}(?:[v.—]|$)|\d{2} ?[-~—] ?\d{2}/i

// create an array of potentially valid titles from a given media
function createTitle (media) {
  // group and de-duplicate
  const grouped = [
    ...new Set(
      Object.values(media.title)
        .concat(media.synonyms)
        .filter(name => name != null && name.length > 3)
    )
  ]
  const titles = []
  const appendTitle = t => {
    // replace & with encoded
    const title = t.replace(/&/g, '%26').replace(/\?/g, '%3F').replace(/#/g, '%23')
    titles.push(title)

    // replace Season 2 with S2, else replace 2nd Season with S2, but keep the original title
    const match1 = title.match(/(\d)(?:nd|rd|th) Season/i)
    const match2 = title.match(/Season (\d)/i)

    if (match2) {
      titles.push(title.replace(/Season \d/i, `S${match2[1]}`))
    } else if (match1) {
      titles.push(title.replace(/(\d)(?:nd|rd|th) Season/i, `S${match1[1]}`))
    }
  }
  for (const t of grouped) {
    appendTitle(t)
    if (t.includes('-')) appendTitle(t.replaceAll('-', ''))
  }
  return titles
}
