import { getCollection, getEntry } from "astro:content";
import type { Lang, PostWebsite, UserWebsite } from "@content/config";
import { getWebsiteLinkForUser } from "./get_website_link_for_user";
import { getUsernameForLang } from "./get_username_for_lang";

type MatchGroups =
  | {
      text: string;
      link: string;
    }
  | {
      text: string;
      link: string;
      contentPrefix: "stories" | "games" | "blog" | "users";
      slug: string;
    };

const LOCAL_URL_REGEX =
  /\[(?<text>[^\]]*)\]\((?<link>(?:\/(?<contentPrefix>stories|games|blog|users)(?!\/?\)|\/[1-9]\d*\/?\))\/(?<slug>[^\)]+))|(?:\/[^\)]+?))\/?\)/g;

const SERIES_URLS_REGEX_PROMISE = getCollection("series").then(
  (series) => new RegExp(`^(${series.map(({ data }) => data.link.replace(/\/$/, "")).join("|")})\/?$`),
);

/**
 * Given a Markdown text, finds any local URLs (eg. `[click me](/stories/foo)`) and replaces them with the appropriate
 * fully qualified link (eg. `[click me](https://example.com/stories/foo)`).
 * @param originalText The Markdown text to modify.
 * @param lang The language for translating usernames.
 * @param site The base URL of the gallery, to append to the start of local links and permalinks.
 * If undefined, this function will only replace `/users/...` links, while still validating the others.
 * @param website Which website the Markdown text will be rendered to. If defined, any content links
 * (stories, games, etc.) will be replaced by the appropriate link on that website whenever possible.
 * If undefined, this function will only replace `/users/...` links (and permalinks if `site` is set),
 * while still validating the others.
 */
export async function qualifyLocalURLsInMarkdown(originalText: string, lang: Lang, site?: URL, website?: PostWebsite) {
  const replacements: string[] = [];
  for (const match of originalText.matchAll(new RegExp(LOCAL_URL_REGEX))) {
    const groups = match.groups as MatchGroups | undefined;
    if (groups?.text === undefined || groups?.link === undefined) {
      throw new Error(`Cannot qualify invalid local URL ${match[0]}`);
    }
    const { text, link } = groups;
    // Any links that match a series `link` are handled separately.
    if ((await SERIES_URLS_REGEX_PROMISE).test(link)) {
      replacements.push(site ? `[${text}](${new URL(link, site).toString()})` : match[0]);
      continue;
    }
    // Check if this is a special link (story, game, or user)
    if ("contentPrefix" in groups && groups.contentPrefix) {
      const { contentPrefix, slug } = groups;
      switch (contentPrefix) {
        case "stories":
          const story = await getEntry("stories", slug);
          if (!story) {
            throw new Error(`Couldn't find story with slug "${slug}"`);
          }
          if (typeof website === "string" && story.data.posts[website as PostWebsite]?.link) {
            replacements.push(`[${text}](${story.data.posts[website as PostWebsite]!.link})`);
            continue;
          }
          break;
        case "games":
          const game = await getEntry("games", slug);
          if (!game) {
            throw new Error(`Couldn't find game with slug "${slug}"`);
          }
          if (typeof website === "string" && game.data.posts[website as PostWebsite]?.link) {
            replacements.push(`[${text}](${game.data.posts[website as PostWebsite]!.link})`);
            continue;
          }
          break;
        case "blog":
          const post = await getEntry("blog", slug);
          if (!post) {
            throw new Error(`Couldn't find blog post with slug "${slug}"`);
          }
          if (typeof website === "string" && post.data.posts[website as PostWebsite]?.link) {
            replacements.push(`[${text}](${post.data.posts[website as PostWebsite]!.link})`);
            continue;
          }
          break;
        case "users":
          const user = await getEntry("users", slug);
          if (!user) {
            throw new Error(`Couldn't find user with id "${slug}"`);
          }
          // If there's a label in the link, use that if possible
          if (text) {
            if (typeof website === "string" && user.data.links[website as UserWebsite]?.link) {
              replacements.push(`[${text}](${user.data.links[website as UserWebsite]!.link})`);
              continue;
            } else if (user.data.preferredLink) {
              replacements.push(`[${text}](${user.data.links[user.data.preferredLink]!.link})`);
              continue;
            }
          }
          // Otherwise (i.e. label is empty), use the username for the website
          replacements.push(
            getWebsiteLinkForUser(user, website as UserWebsite, (user) => getUsernameForLang(user, lang)),
          );
          continue;
        default:
          const unknown: never = contentPrefix;
          console.warn(`WARNING: Unknown local link prefix ${unknown}; ignoring...`);
      }
    }
    replacements.push(website && site ? `[${text}](${new URL(link, site).toString()})` : match[0]);
  }
  const newText = originalText.replaceAll(new RegExp(LOCAL_URL_REGEX), () => {
    const replacement = replacements.shift();
    if (!replacement) {
      throw new Error(`Replacements array length didn't match length of regex matches! (too few elements)`);
    }
    return replacement;
  });
  if (replacements.length > 0) {
    throw new Error(`Replacements array length didn't match length of regex matches! (too many elements)`);
  }
  return newText;
}
