import rss, { type RSSFeedItem } from "@astrojs/rss";
import type { APIRoute } from "astro";
import { getCollection, getEntries, type CollectionEntry, type CollectionKey } from "astro:content";
import { markdown } from "@astropub/md";
import sanitizeHtml from "sanitize-html";
import { t, type Lang } from "../i18n";
import { markdownToPlaintext } from "../utils/markdown_to_plaintext";
import { getUsernameForLang } from "../utils/get_username_for_lang";
import { qualifyLocalURLsInMarkdown } from "../utils/qualify_local_urls_in_markdown";

type FeedItem = RSSFeedItem &
  Required<Pick<RSSFeedItem, "title" | "pubDate" | "link" | "description" | "categories" | "content">>;

type EntryWithPubDate<C extends CollectionKey> = CollectionEntry<C> & { data: { pubDate: Date } };

const MAX_ITEMS = 6;

function toNoonUTCDate(date: Date) {
  const adjustedDate = new Date(date);
  adjustedDate.setUTCHours(12);
  return adjustedDate;
}

const getLinkForUser = (user: CollectionEntry<"users">, lang: Lang) => {
  const userName = getUsernameForLang(user, lang);
  if (user.data.preferredLink) {
    return `<a href="${user.data.links[user.data.preferredLink]!.link}">${userName}</a>`;
  }
  return userName;
};

async function storyFeedItem(
  site: URL | undefined,
  data: EntryWithPubDate<"stories">["data"],
  slug: CollectionEntry<"stories">["slug"],
  body: string,
): Promise<FeedItem> {
  return {
    title: `New story! "${data.title}"`,
    pubDate: toNoonUTCDate(data.pubDate),
    link: `/stories/${slug}`,
    description:
      `${t(data.lang, "story/warnings", data.wordCount, data.contentWarning)}\n\n${markdownToPlaintext(await qualifyLocalURLsInMarkdown(data.description, data.lang, site))}`.replaceAll(
        /[\n ]+/g,
        " ",
      ),
    categories: ["story"],
    commentsUrl: data.posts.mastodon?.link,
    content: sanitizeHtml(
      `<h1>${data.title}</h1>` +
        `<p>${t(
          data.lang,
          "story/authors",
          (await getEntries(data.authors)).map((author) => getLinkForUser(author, data.lang)),
        )}</p>` +
        (data.requesters
          ? `<p>${t(
              data.lang,
              "story/requested_by",
              (await getEntries(data.requesters)).map((requester) => getLinkForUser(requester, data.lang)),
            )}</p>`
          : "") +
        (data.commissioners
          ? `<p>${t(
              data.lang,
              "story/commissioned_by",
              (await getEntries(data.commissioners)).map((commissioner) => getLinkForUser(commissioner, data.lang)),
            )}</p>`
          : "") +
        `<hr><p><em>${t(data.lang, "story/warnings", data.wordCount, data.contentWarning)}</em></p>` +
        `<hr>${await markdown(body)}` +
        `<hr>${await markdown(await qualifyLocalURLsInMarkdown(data.description, data.lang, site))}`,
    ),
  };
}

async function gameFeedItem(
  site: URL | undefined,
  data: EntryWithPubDate<"games">["data"],
  slug: CollectionEntry<"games">["slug"],
  body: string,
): Promise<FeedItem> {
  return {
    title: `New game! "${data.title}"`,
    pubDate: toNoonUTCDate(data.pubDate),
    link: `/games/${slug}`,
    description:
      `${t(data.lang, "game/warnings", data.platforms, data.contentWarning)}\n\n${markdownToPlaintext(await qualifyLocalURLsInMarkdown(data.description, data.lang, site))}`.replaceAll(
        /[\n ]+/g,
        " ",
      ),
    categories: ["game"],
    commentsUrl: data.posts.mastodon?.link,
    content: sanitizeHtml(
      `<h1>${data.title}</h1>` +
        `<p>${t(
          data.lang,
          "story/authors",
          (await getEntries(data.authors)).map((author) => getLinkForUser(author, data.lang)),
        )}</p>` +
        `<hr><p>${t(data.lang, "game/platforms", data.platforms)}</p>` +
        `<hr><p><em>${data.contentWarning}</em></p>` +
        `<hr>${await markdown(body)}` +
        `<hr>${await markdown(await qualifyLocalURLsInMarkdown(data.description, data.lang, site))}`,
    ),
  };
}

export const GET: APIRoute = async ({ site }) => {
  const stories = (
    (await getCollection(
      "stories",
      (story) => !story.data.isDraft && story.data.pubDate,
    )) as EntryWithPubDate<"stories">[]
  )
    .sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
    .slice(0, MAX_ITEMS);
  const games = (
    (await getCollection("games", (game) => !game.data.isDraft && game.data.pubDate)) as EntryWithPubDate<"games">[]
  )
    .sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
    .slice(0, MAX_ITEMS);

  return rss({
    title: "Gallery | Bad Manners",
    description: "Stories, games, and (possibly) more by Bad Manners",
    site: site!,
    items: await Promise.all(
      [
        stories.map(({ data, slug, body }) => ({
          date: data.pubDate,
          fn: () => storyFeedItem(site, data, slug, body),
        })),
        games.map(({ data, slug, body }) => ({
          date: data.pubDate,
          fn: () => gameFeedItem(site, data, slug, body),
        })),
      ]
        .flat()
        .sort((a, b) => b.date.getTime() - a.date.getTime())
        .slice(0, MAX_ITEMS)
        .map((value) => value.fn()),
    ),
  });
};
