---
import type { ImageMetadata } from "astro";
import { getImage } from "astro:assets";
import { type CollectionEntry, getEntry, getCollection } from "astro:content";
import { Markdown } from "@astropub/md";
import { slug } from "github-slugger";
import { DEFAULT_LANG, t, type Lang } from "@i18n";
import BaseLayout from "@layouts/BaseLayout.astro";
import CopyrightedCharacters from "@components/CopyrightedCharacters.astro";
import Prose from "@components/Prose.astro";
import MastodonComments from "@components/MastodonComments.astro";
import type { CopyrightedCharacters as CopyrightedCharactersType, Posts } from "src/content.config";
import { qualifyLocalURLsInMarkdown } from "@utils/qualify_local_urls_in_markdown";
import {
  IconSun,
  IconMoon,
  IconCircleInfo,
  IconArrowBack,
  IconChevronLeft,
  IconChevronRight,
  IconArrowUp,
} from "@components/icons";
import ExternalPosts from "@components/ExternalPosts.astro";
import { markdownToPlaintext } from "@utils/markdown_to_plaintext";

interface RelatedContent {
  link: string;
  title: string;
}

type Props = {
  /* Content attributes */
  title: string;
  lang: Lang;
  isDraft: boolean;
  isAgeRestricted: boolean;
  pubDate?: Date;
  description?: string;
  summary?: string;
  tags: string[];
  thumbnail?: ImageMetadata;
  thumbnailWidth?: number;
  thumbnailHeight?: number;
  copyrightedCharacters?: CopyrightedCharactersType;
  series?: CollectionEntry<"series">;
  prev?: RelatedContent;
  next?: RelatedContent;
  relatedStories?: CollectionEntry<"stories">[];
  relatedGames?: CollectionEntry<"games">[];
  relatedBlogPosts?: CollectionEntry<"blog">[];
  posts: Posts;
  authors: CollectionEntry<"users">[];

  /* Layout attributes */
  publishedContentType: "story" | "game" | "blog post";
  labelReturnTo: RelatedContent;
  labelPreviousContent: string;
  labelNextContent: string;
  labelTitleSection: string;
  labelInformationSection: string;
  labelArticleSection: string;
};

const { props } = Astro;
const series = props.series && (await getEntry(props.series));
const categorizedTags: Record<string, { name: string | null; description?: string }> = Object.fromEntries(
  (await getCollection("tag-categories")).flatMap((category) =>
    category.data.tags.map<[string, { name: string | null; description?: string }]>(({ name, description }) =>
      typeof name === "string"
        ? [
            name,
            {
              name,
              description:
                typeof description === "object" ? (description[props.lang] ?? description[DEFAULT_LANG]) : description,
            },
          ]
        : [
            name[DEFAULT_LANG],
            {
              name: name[props.lang] ?? null,
              description:
                typeof description === "object" ? (description[props.lang] ?? description[DEFAULT_LANG]) : description,
            },
          ],
    ),
  ),
);
const description = props.description && (await qualifyLocalURLsInMarkdown(props.description, props.lang));
const summary = props.summary && (await qualifyLocalURLsInMarkdown(props.summary, props.lang));
const tags = props.tags.map<{ id: string; name: string; description?: string }>((tag) => {
  const tagSlug = slug(tag);
  if (!(tag in categorizedTags)) {
    console.warn(`WARNING: Tag "${tag}" doesn't have a category in the "tag-categories" collection`);
    return { id: tagSlug, name: tag, description: undefined };
  }
  const { name, description } = categorizedTags[tag]!;
  if (name == null) {
    console.warn(`WARNING: No "${props.lang}" translation for tag "${tag}"`);
    return { id: tagSlug, name: tag, description };
  }
  return { id: tagSlug, name, description };
});
const thumbnail =
  props.thumbnail &&
  (await getImage({ src: props.thumbnail, width: props.thumbnailWidth, height: props.thumbnailHeight }));

const returnTo = series ? t(props.lang, "published_content/return_to_series") : props.labelReturnTo.title;
---

<BaseLayout pageTitle={props.title} lang={props.lang} isAgeRestricted={props.isAgeRestricted}>
  <Fragment slot="head">
    {props.isDraft ? <meta name="robots" content="noindex" /> : null}
    <meta property="og:title" content={props.title} data-pagefind-meta="title[content]" />
    <meta property="og:url" content={Astro.url} data-pagefind-meta="url[content]" />
    {
      thumbnail ? (
        <Fragment>
          <meta content={thumbnail.src} property="og:image" data-pagefind-meta="image[content]" />
          <meta
            property="og:image:alt"
            content={t(props.lang, "published_content/cover_art_alt", props.title)}
            data-pagefind-meta="image_alt[content]"
          />
        </Fragment>
      ) : null
    }
    {
      props.authors
        .map((user) => user.data.links.mastodon?.username)
        .filter((value) => value)
        .map((username) => <meta name="fediverse:creator" content={`@${username}`} />)
    }
    <slot name="head" />
  </Fragment>
  <div
    id="top"
    class="min-w-screen relative min-h-screen bg-radial from-bm-300 to-bm-600 px-1 pb-24 pt-28 sm:px-4 md:px-6 lg:pt-32 dark:from-green-700 dark:to-green-950 print:bg-none print:pb-0 print:pt-0"
  >
    <div
      id="toolbox-buttons"
      aria-label="Toolbox"
      class="pointer-events-none absolute top-0 h-[80vh] py-2 pl-2 lg:inset-y-0 lg:h-full"
    >
      <div
        class="pointer-events-auto sticky top-6 flex flex-col rounded-xl bg-white px-1 py-1 shadow-md dark:bg-black print:hidden"
      >
        <a href={series ? series.data.link : props.labelReturnTo.link} class="text-link mx-1 flex items-center p-2">
          <IconArrowBack width="1.25rem" height="1.25rem" />
          <span class="ml-1 text-sm">{returnTo}</span>
        </a>
        <a href="#meta" class="text-link mx-1 flex items-center border-t border-stone-300 p-2 dark:border-stone-700">
          <IconCircleInfo width="1.25rem" height="1.25rem" />
          <span class="ml-1 text-sm">{t(props.lang, "published_content/go_to_description")}</span>
        </a>
        <button
          class="text-link mx-1 flex items-center border-t border-stone-300 p-2 dark:border-stone-700"
          @click="darkMode = !darkMode; localStorage.colorScheme = darkMode ? 'dark' : 'light'"
        >
          <IconSun width="1.25rem" height="1.25rem" class="hidden dark:block" />
          <IconMoon width="1.25rem" height="1.25rem" class="block dark:hidden" />
          <span class="ml-1 text-sm">{t(props.lang, "published_content/toggle_dark_mode")}</span>
        </button>
      </div>
    </div>
    <main
      class="h-entry mx-auto max-w-6xl rounded-lg bg-stone-50 px-2 pb-10 shadow-sm sm:px-6 md:px-32 lg:px-64 dark:bg-stone-900 print:max-w-full print:bg-none print:shadow-none"
      data-pagefind-body={props.isDraft ? undefined : ""}
      data-language={t(props.lang, "language/language")}
      data-type={props.publishedContentType}
      data-pagefind-filter="type[data-type], language[data-language]"
    >
      {
        props.prev || props.next ? (
          <div class="print:hidden">
            <div id="nav-top" class="my-4 grid grid-cols-2 justify-items-stretch gap-2 pt-4 text-lg font-light">
              {props.prev ? (
                <a
                  href={props.prev.link}
                  rel="prev"
                  class="h-entry u-url text-link flex items-center justify-center border-r border-stone-400 px-1 py-3 underline dark:border-stone-600"
                  aria-label={props.labelPreviousContent}
                >
                  <IconChevronLeft width="1.25rem" height="1.25rem" />
                  <span class="p-name ml-1">{props.prev.title}</span>
                </a>
              ) : (
                <div class="h-full border-r border-stone-400 dark:border-stone-600" aria-hidden="true" />
              )}
              {props.next ? (
                <a
                  href={props.next.link}
                  rel="next"
                  class="h-entry u-url text-link flex items-center justify-center px-1 py-3 underline"
                  aria-label={props.labelNextContent}
                >
                  <span class="p-name mr-1">{props.next.title}</span>
                  <IconChevronRight width="1.25rem" height="1.25rem" />
                </a>
              ) : (
                <div aria-hidden="true" />
              )}
            </div>
            <hr class="mx-auto mb-5 w-full max-w-2xl border-stone-400 dark:border-stone-600" />
          </div>
        ) : (
          <div class="pt-5 sm:pt-7" aria-hidden="true" />
        )
      }
      <h1
        id="section-title"
        class="p-name px-1 pt-4 font-serif text-4xl font-medium text-stone-800 dark:text-stone-200"
        aria-label={props.labelTitleSection}
      >
        {props.title}
      </h1>
      <hr class="mb-3 ml-[2px] mt-2 h-[4px] w-1/2 rounded-sm bg-stone-800 dark:bg-stone-200" />
      <section
        id="section-information"
        class="p-summary mt-1 space-y-2 px-2 font-serif font-light italic text-stone-700 dark:text-stone-300"
        aria-label={props.labelInformationSection}
      >
        <slot name="section-information" />
        {
          props.isDraft ? (
            <p id="draft-warning" class="py-2 text-center text-2xl font-semibold not-italic text-red-600">
              {t(props.lang, "published_content/draft_warning")}
            </p>
          ) : null
        }
        <slot name="section-content-warning" />
      </section>
      {
        thumbnail ? (
          <Fragment>
            <hr class="mx-auto my-10 w-[80%] max-w-xl border-stone-400 dark:border-stone-600" />
            <img
              loading="eager"
              src={thumbnail.src}
              alt={t(props.lang, "published_content/cover_art_alt", props.title)}
              width={props.thumbnailWidth}
              height={props.thumbnailHeight}
              class="u-photo mx-auto my-10 shadow-lg sm:my-16"
            />
          </Fragment>
        ) : null
      }
      <hr class="mx-auto my-10 w-[80%] max-w-xl border-stone-400 dark:border-stone-600" />
      <article id="content" class="e-content pr-1 font-serif" aria-label={props.labelArticleSection}>
        <slot name="section-article" />
      </article>
      <hr class="mx-auto mb-6 mt-10 w-[80%] max-w-xl border-stone-400 dark:border-stone-600" />
      {
        props.isDraft ? (
          <p
            id="draft-warning-bottom"
            class="py-2 text-center font-serif text-2xl font-semibold not-italic text-red-600"
          >
            {t(props.lang, "published_content/draft_warning")}
          </p>
        ) : props.pubDate ? (
          <time
            id="publish-date"
            datetime={props.pubDate.toISOString().slice(0, 10)}
            class="dt-published mt-2 block px-2 text-center font-serif font-light text-stone-600 dark:text-stone-200"
            aria-label={t(props.lang, "published_content/publish_date_aria_label")}
            aria-description={
              t(props.lang, "published_content/publish_date_aria_description", props.pubDate) || undefined
            }
            data-pagefind-index-attrs="aria-description"
            data-pagefind-meta="date[datetime]"
          >
            {t(props.lang, "published_content/publish_date", props.pubDate)}
          </time>
        ) : null
      }
      <div id="meta"></div>
      {
        description ? (
          <section id="description" class="px-2 font-serif" aria-describedby="title-description">
            <h2 id="title-description" class="py-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
              {t(props.lang, "published_content/description")}
            </h2>
            <Prose>
              <Markdown of={description} />
              <CopyrightedCharacters copyrightedCharacters={props.copyrightedCharacters} lang={props.lang} />
            </Prose>
          </section>
        ) : null
      }
      {
        summary ? (
          <section id="summary" class="px-2 font-serif" aria-describedby="title-summary">
            <h2 id="title-summary" class="py-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
              {t(props.lang, "published_content/summary")}
            </h2>
            <details class="mb-6 mt-1 rounded-lg border border-stone-400 bg-stone-50 text-stone-800 dark:border-stone-500 dark:bg-stone-900 dark:text-stone-100">
              <summary class="rounded-lg bg-stone-200 px-2 py-1 dark:bg-stone-800">
                {t(props.lang, "published_content/reveal_summary")}
              </summary>
              <div class="px-2 py-1">
                <Prose>
                  <Markdown of={summary} />
                </Prose>
              </div>
            </details>
          </section>
        ) : null
      }
      <div class="pr-3 text-right print:hidden">
        <a href="#top" class="text-link inline-flex items-center underline" aria-labelledby="label-to-top">
          <IconArrowUp width="1.25rem" height="1.25rem" />
          <span class="ml-1" id="label-to-top">{t(props.lang, "published_content/to_top")}</span>
        </a>
      </div>
      {
        props.prev || props.next ? (
          <Fragment>
            <hr class="mx-auto mt-5 w-full max-w-2xl border-stone-400 dark:border-stone-600" />
            <div id="nav-bottom" class="my-4 grid grid-cols-2 justify-items-stretch gap-2 text-lg font-light">
              {props.prev ? (
                <a
                  href={props.prev.link}
                  rel="prev"
                  class="text-link flex items-center justify-center border-r border-stone-400 px-1 py-3 underline dark:border-stone-600"
                  aria-label={props.labelPreviousContent}
                >
                  <IconChevronLeft width="1.25rem" height="1.25rem" />
                  <span class="ml-1">{props.prev.title}</span>
                </a>
              ) : (
                <div class="h-full border-r border-stone-400 dark:border-stone-600" aria-hidden="true" />
              )}
              {props.next ? (
                <a
                  href={props.next.link}
                  rel="next"
                  class="text-link flex items-center justify-center px-1 py-3 underline"
                  aria-label={props.labelNextContent}
                >
                  <span class="mr-1">{props.next.title}</span>
                  <IconChevronRight width="1.25rem" height="1.25rem" />
                </a>
              ) : (
                <div aria-hidden="true" />
              )}
            </div>
            <hr class="mx-auto mb-5 w-full max-w-2xl border-stone-400 dark:border-stone-600" />
          </Fragment>
        ) : null
      }
      {
        props.relatedStories?.length ? (
          <section id="related-stories" aria-describedby="title-related-stories" class="my-5">
            <h2
              id="title-related-stories"
              class="p-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100"
            >
              {t(props.lang, "published_content/related_stories")}
            </h2>
            <Prose>
              <ul class="font-serif">
                {props.relatedStories.map((story) => (
                  <li>
                    <a href={`/stories/${story.id}`}>{story.data.title}</a>
                  </li>
                ))}
              </ul>
            </Prose>
          </section>
        ) : null
      }
      {
        props.relatedGames?.length ? (
          <section id="related-games" aria-describedby="title-related-games" class="my-5">
            <h2
              id="title-related-games"
              class="p-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100"
            >
              {t(props.lang, "published_content/related_games")}
            </h2>
            <Prose>
              <ul class="font-serif">
                {props.relatedGames.map((game) => (
                  <li>
                    <a href={`/games/${game.id}`}>{game.data.title}</a>
                  </li>
                ))}
              </ul>
            </Prose>
          </section>
        ) : null
      }
      {
        props.relatedBlogPosts?.length ? (
          <section id="related-blog-posts" aria-describedby="title-related-blog-posts" class="my-5">
            <h2
              id="title-related-blog-posts"
              class="p-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100"
            >
              {t(props.lang, "published_content/related_blog_posts")}
            </h2>
            <Prose>
              <ul class="font-serif">
                {props.relatedBlogPosts.map((post) => (
                  <li>
                    <a href={`/blog/${post.id}`}>{post.data.title}</a>
                  </li>
                ))}
              </ul>
            </Prose>
          </section>
        ) : null
      }
      {
        tags.length ? (
          <section id="tags" aria-describedby="title-tags" class="my-5">
            <h2 id="title-tags" class="p-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
              {t(props.lang, "published_content/tags")}
            </h2>
            <ul class="p-category flex flex-wrap gap-x-2 gap-y-3 px-3">
              {tags.map(({ id, name, description }) => (
                <li>
                  <a
                    class="rounded-full bg-bm-300 px-3 py-1 text-sm text-black shadow-sm hover:underline focus:underline dark:bg-green-700 dark:text-white print:bg-none"
                    href={`/tags/${id}`}
                    rel="tag"
                    title={description ? markdownToPlaintext(description) : undefined}
                    data-tooltip={description ? "" : undefined}
                  >
                    {name}
                  </a>
                </li>
              ))}
            </ul>
          </section>
        ) : null
      }
      <ExternalPosts lang={props.lang} posts={props.posts} />
      {props.posts.mastodon ? <MastodonComments lang={props.lang} {...props.posts.mastodon} /> : null}
    </main>
    <footer
      class="pt-14 text-center text-xs text-black contrast-more:bg-bm-300 dark:text-white dark:contrast-more:bg-green-950"
      aria-label={t(props.lang, "published_content/copyright_aria_label")}
    >
      <span
        set:html={t(props.lang, "published_content/copyright_year", (props.pubDate || new Date()).getFullYear())}
      /><span>&nbsp;|</span>
      <a class="hover:underline focus:underline" href="/licenses.toml" rel="license"
        >{t(props.lang, "published_content/licenses")}</a
      >
    </footer>
  </div>
</BaseLayout>

<script>
  import tippy from "tippy.js";
  import "tippy.js/dist/tippy.css";

  const tooltipItems = document.querySelectorAll<HTMLElement>("[title][data-tooltip]");
  tooltipItems.forEach((el) => el.setAttribute("data-tooltip", el.title));
  tippy(tooltipItems, {
    content: (el) => (el as HTMLElement).dataset.tooltip!,
    theme: "bm",
  });
  tooltipItems.forEach((el) => el.removeAttribute("title"));
</script>
