import { Lang } from "@/constants/languages"
import { env } from "@/env/server"
import { createCounter } from "@/server/telemetry"

import { removeTrailingSlash } from "@/utils/url"

import type { SitemapEntry, SyncItem } from "@/types/sitemap"

export function mergeEntries(
  currentEntries: SyncItem[],
  newEntries: SyncItem[]
) {
  const entriesMergeCounter = createCounter("sitemap", "entries.merge")
  const metricsEntriesMerge = entriesMergeCounter.init({
    currentEntriesCount: currentEntries.length,
    newEntriesCount: newEntries.length,
  })

  metricsEntriesMerge.start()

  const entries = [...currentEntries]
  newEntries.forEach((entry) => {
    const index = entries.findIndex(
      ({ data }) =>
        data.uid === entry.data.uid && data.locale === entry.data.locale
    )
    if (index > -1) {
      entries[index] = entry
    } else {
      entries.push(entry)
    }
  })

  metricsEntriesMerge.success({
    entriesCount: entries.length,
  })

  return entries
}

export function mapEntriesToSitemapData(entries: SyncItem[]) {
  const entriesTransformCounter = createCounter("sitemap", "entries.transform")
  const metricsEntriesTransform = entriesTransformCounter.init({
    entriesCount: entries.length,
  })

  metricsEntriesTransform.start()

  const filteredEntries = filterEntriesToSitemapEntries(entries)

  const entriesByUid = groupEntriesByUid(filteredEntries)
  const sitemapEntries = Object.entries(entriesByUid)
    .map(([_, entries]) => mapEntriesToSitemapEntry(entries))
    .filter((entry): entry is SitemapEntry => !!entry)

  metricsEntriesTransform.success({
    entriesCount: entries.length,
    sitemapEntriesCount: sitemapEntries.length,
  })

  return sitemapEntries
}

function filterEntriesToSitemapEntries(entries: SyncItem[]) {
  return entries.filter((entry: SyncItem) => {
    const shouldIndex = !entry.data.web?.seo_metadata?.noindex
    return !!(entry.type === "entry_published" && entry.data.url && shouldIndex)
  })
}

// We group the entries by UID because Contentstack has the same `uid` for an
// entry regardless of locale. We want to display each locale as an alternate
// in the sitemap, therefore we group them here by `uid`.
function groupEntriesByUid(entries: SyncItem[]) {
  return entries.reduce<Record<string, SyncItem[]>>((acc, entry) => {
    const uid = entry.data.uid
    if (!acc[uid]) {
      acc[uid] = []
    }
    acc[uid].push(entry)
    return acc
  }, {})
}

function mapEntriesToSitemapEntry(entries: SyncItem[]) {
  // Main entry is always English. Without English, there can't be other pages in ContentStack.
  const mainEntry = entries.find((entry) => entry.data.locale === Lang.en)
  const alternates = getAlternates(entries)
  const lastModified = getLastModified(entries)
  const changeFrequency = getChangeFrequency(entries)
  const priority = getPriority(entries)

  if (mainEntry) {
    const { locale, url } = mainEntry.data
    const sitemapEntry: SitemapEntry = {
      url: removeTrailingSlash(`${env.PUBLIC_URL}/${locale}${url}`),
      lastModified,
      changeFrequency,
      priority,
      alternates,
    }
    return sitemapEntry
  }
}

function getLastModified(entries: SyncItem[]) {
  // Localized versions of the data can have a different last modified value.
  // We make sure we take the latest.
  return entries.reduce((latest, entry) => {
    const entryDate = entry.data.updated_at
    return entryDate > latest ? entryDate : latest
  }, "")
}

function getChangeFrequency(entries: SyncItem[]) {
  // Localized versions of the data can have a different changeFrequency value.
  // We make sure we take the highest.
  const changeFrequencyPriority: SitemapEntry["changeFrequency"][] = [
    "never",
    "yearly",
    "monthly",
    "weekly",
    "daily",
    "hourly",
    "always",
  ]
  return entries.reduce<SitemapEntry["changeFrequency"]>((highest, entry) => {
    const changeFrequency =
      entry.data.web?.seo_metadata?.sitemap?.change_frequency ?? "daily"
    return changeFrequencyPriority.indexOf(changeFrequency) >
      changeFrequencyPriority.indexOf(highest)
      ? changeFrequency
      : highest
  }, "never")
}

function getPriority(entries: SyncItem[]) {
  // Localized versions of the data can have a different priority.
  // We make sure we take the highest.
  return entries.reduce((highest, entry) => {
    const priority = entry.data.web?.seo_metadata?.sitemap?.priority ?? 0.5
    return priority > highest ? priority : highest
  }, 0.0)
}

function getAlternates(entries: SyncItem[]) {
  return entries
    .filter((entry) => entry.data.locale !== Lang.en)
    .reduce<Partial<Record<Lang, string>>>((acc, { data }) => {
      acc[data.locale] = `${env.PUBLIC_URL}/${data.locale}${data.url}`
      return acc
    }, {})
}
