---
import type { Lang } from "@i18n";
import { IconStar, IconRetweet } from "./icons";
import { getCollection } from "astro:content";
import { isUserSelf } from "@utils/is_user_self";

type Props = {
  lang: Lang;
  link: string;
  instance: string;
  user: string;
  postId: string;
  blacklistedComments?: string[];
};

const { link, instance, user, postId, blacklistedComments } = Astro.props;
const selfUrl = (await getCollection("users", (user) => isUserSelf(user)))[0]?.data.links.mastodon?.link;
---

<section
  id="comments-section"
  class="px-2 font-serif"
  aria-describedby="title-comments-section"
  x-data={JSON.stringify({
    link,
    instance,
    user,
    postId,
    blacklistedComments: blacklistedComments ?? [],
    selfUrl,
    comments: null,
    commentsLoading: false,
    commentsError: null,
  })}
  @mastodon-comments.window="comments = $event.detail.comments; commentsLoading = false"
  @mastodon-comments-error.window="commentsError = $event.detail.error"
>
  <h2 id="title-comments-section" class="py-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
    Comments
  </h2>
  <p id="comments-description" class="my-1 text-stone-800 dark:text-stone-100">
    <span x-show="false"
      ><a class="u-syndication text-link underline" href={link} target="_blank">View comments on Mastodon</a>.</span
    >
    <template x-if="commentsError === null && comments !== null && comments.length == 0">
      <span
        >No comments yet. <a class="text-link underline" href={link} target="_blank"
          >Be the first to join the conversation on Mastodon</a
        >.</span
      >
    </template>
    <template x-if="commentsError === null && comments !== null && comments.length > 0"
      ><span>
        Join the conversation <a class="text-link underline" href={link} target="_blank">by replying on Mastodon</a
        >.</span
      ></template
    >
    <template x-if="commentsError">
      <span>Unable to load comments. Please try again later.</span>
    </template>
  </p>
  <template x-if="comments === null && !commentsError">
    <button
      class="group mx-auto w-64 rounded-lg bg-bm-300 px-4 py-1 text-stone-800 disabled:bg-bm-400 dark:bg-green-800 dark:text-stone-100 dark:disabled:bg-green-900"
      id="load-comments-button"
      @click="if (!$el.disabled) { $el.disabled = true; commentsLoading = true; $dispatch('retrieveComments', { link, instance, user, postId, blacklistedComments, selfUrl })}"
      :aria-pressed="commentsLoading"
    >
      <span class="block hover:underline group-focus:underline group-disabled:hidden">Click to load comments</span>
      <span class="hidden group-disabled:block">
        <svg
          style={{ width: "1.25rem", height: "1.25rem", display: "inline" }}
          class="-mt-1 mr-1 animate-spin"
          fill="none"
          viewBox="0 0 24 24"
          aria-hidden="true"
        >
          <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
          <path
            class="opacity-100"
            fill="currentColor"
            d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
          ></path>
        </svg>
        Loading...
      </span>
    </button>
  </template>
  <div id="comments" x-show="comments !== null && comments.length > 0">
    <template x-for="comment in comments">
      <div x-html="comment"></div>
    </template>
  </div>
</section>

<template id="template-comment-emoji">
  <picture>
    <source media="(prefers-reduced-motion: reduce)" />
    <img width={20} class="mx-[1px] inline" />
  </picture>
</template>

<template id="template-comment-box">
  <div
    role="article"
    class="p-comment h-entry my-2 rounded-md border-2 border-stone-400 bg-stone-100 p-2 text-stone-800 dark:border-stone-600 dark:bg-stone-800 dark:text-stone-100"
  >
    <div class="ml-1">
      <a
        data-author
        class="p-author h-card u-url text-link flex items-center text-lg hover:underline focus:underline"
        target="_blank"
      >
        <picture>
          <source data-avatar-static media="(prefers-reduced-motion: reduce)" />
          <img data-avatar width={40} class="u-photo mr-2 rounded-full border border-stone-400 dark:border-stone-600" />
        </picture>
        <span data-display-name class="p-nickname" aria-label="Display name"></span>
      </a>
      <a
        data-post-link
        class="u-url text-link my-1 flex items-center text-sm font-light hover:underline focus:underline"
        rel="nofollow"
        target="_blank"
      >
        <time class="dt-published mr-1" data-published-date aria-label="Publish date"></time>
        <time hidden class="italic" data-edited-date>(edited)</time>
      </a>
    </div>
    <div
      data-content
      class="e-content prose-a:text-link prose-story prose my-1 dark:prose-invert prose-img:my-0"
      aria-label="Comment body"
    >
    </div>
    <div class="ml-1 flex flex-row pb-2 pt-1">
      <span aria-label="Favorites">
        <span data-favorites></span>
        <IconStar width="1.25rem" height="1.25rem" class="ml-1 inline align-text-bottom" />
      </span>
      <span aria-label="Reblogs" class="ml-6">
        <span data-reblogs></span>
        <IconRetweet width="1.25rem" height="1.25rem" class="ml-1 inline align-text-bottom" />
      </span>
    </div>
    <div data-comment-thread class="-mb-2" aria-hidden="true" aria-label="Replies"></div>
  </div>
</template>

<script>
  interface MastodonPost {
    link: string;
    instance: string;
    user: string;
    postId: string;
  }

  interface CustomEmoji {
    shortcode: string;
    url: string;
    static_url: string;
  }

  interface Status {
    id: string;
    in_reply_to_id: string;
    url: string;
    favourites_count: number;
    reblogs_count: number;
    created_at: string;
    edited_at: string | null;
    language: string | null;
    account: {
      username: string;
      acct: string;
      display_name: string;
      url: string;
      avatar: string;
      avatar_static: string;
      emojis: CustomEmoji[];
    };
    content: string;
    emojis: CustomEmoji[];
  }

  interface StatusContext {
    ancestors: Status[];
    descendants: Status[];
  }

  type RetrieveCommentsEvent = CustomEvent<{
    link: string;
    instance: string;
    user: string;
    postId: string;
    blacklistedComments: string[];
    selfUrl?: string;
  }>;

  if (document.querySelector("#comments-section")) {
    async function renderComments(post: MastodonPost, blacklistedComments: Set<string>, selfUrl: string | undefined) {
      try {
        const response = await fetch(`https://${post.instance}/api/v1/statuses/${post.postId}/context`);
        if (!response.ok) {
          throw new Error(`Received error status ${response.status} - ${response.statusText}!`);
        }
        const data: StatusContext = await response.json();

        const emojiTemplate = document.querySelector<HTMLElementTagNameMap["template"]>(
          "template#template-comment-emoji",
        )!;
        const emojiMap: Record<string, string> = {};
        const replaceEmojis = (text: string, emojis: CustomEmoji[]) =>
          emojis.reduce((acc, emoji) => {
            let emojiHTML = emojiMap[emoji.url];
            if (!emojiHTML) {
              const emojiPicture = emojiTemplate.content.cloneNode(true) as DocumentFragment;
              const emojiStatic = emojiPicture.querySelector("source")!;
              emojiStatic.srcset = emoji.static_url;
              const emojiImg = emojiPicture.querySelector("img")!;
              emojiImg.src = emoji.url;
              emojiImg.alt = `:${emoji.shortcode}: emoji`;
              emojiHTML = emojiPicture.firstElementChild!.outerHTML;
              emojiMap[emoji.url] = emojiHTML;
            }
            return acc.replaceAll(`:${emoji.shortcode}:`, emojiHTML);
          }, text);
        const commentsList: DocumentFragment[] = [];
        const commentMap: Record<string, number> = {};
        const commentTemplate = document.querySelector<HTMLElementTagNameMap["template"]>(
          "template#template-comment-box",
        )!;

        data.descendants.forEach((comment) => {
          if (blacklistedComments.has(comment.id)) {
            return;
          }
          if (blacklistedComments.has(comment.in_reply_to_id)) {
            blacklistedComments.add(comment.id);
            return;
          }
          const commentBox = commentTemplate.content.cloneNode(true) as DocumentFragment;
          commentBox.firstElementChild!.id = `comment-${comment.id}`;

          const commentBoxAuthor = commentBox.querySelector<HTMLElementTagNameMap["a"]>("a[data-author]")!;
          commentBoxAuthor.href = comment.account.url;
          if (comment.account.url === selfUrl) {
            commentBoxAuthor.rel = "me";
          } else {
            commentBoxAuthor.rel = "nofollow";
          }
          commentBoxAuthor.title = comment.account.acct;
          commentBoxAuthor.setAttribute("aria-label", comment.account.acct);
          const avatar = commentBoxAuthor.querySelector<HTMLElementTagNameMap["img"]>("img[data-avatar]")!;
          avatar.src = comment.account.avatar;
          avatar.alt = `Avatar of @${comment.account.acct}`;
          const avatarStatic =
            commentBoxAuthor.querySelector<HTMLElementTagNameMap["source"]>("source[data-avatar-static]")!;
          avatarStatic.srcset = comment.account.avatar_static;
          const displayName = commentBoxAuthor.querySelector<HTMLElementTagNameMap["span"]>("span[data-display-name]")!;
          displayName.insertAdjacentHTML(
            "afterbegin",
            replaceEmojis(comment.account.display_name, comment.account.emojis),
          );

          const commentBoxPostLink = commentBox.querySelector<HTMLElementTagNameMap["a"]>("a[data-post-link]")!;
          commentBoxPostLink.href = comment.url;
          const publishDate =
            commentBoxPostLink.querySelector<HTMLElementTagNameMap["time"]>("time[data-published-date]")!;
          publishDate.dateTime = comment.created_at;
          publishDate.insertAdjacentText(
            "afterbegin",
            new Date(Date.parse(comment.created_at)).toLocaleString("en-US", {
              month: "short",
              day: "numeric",
              year: "numeric",
              hour: "2-digit",
              minute: "2-digit",
            }),
          );

          if (comment.edited_at) {
            const edited = commentBoxPostLink.querySelector<HTMLElementTagNameMap["time"]>("time[data-edited-date]")!;
            edited.dateTime = comment.edited_at;
            edited.title = comment.edited_at;
            edited.classList.remove("hidden");
            edited.classList.add("dt-updated");
            edited.hidden = false;
          }

          const commentBoxContent = commentBox.querySelector<HTMLElementTagNameMap["div"]>("div[data-content]")!;
          commentBoxContent.insertAdjacentHTML("afterbegin", replaceEmojis(comment.content, comment.emojis));

          const commentBoxFavorites = commentBox.querySelector<HTMLElementTagNameMap["span"]>("span[data-favorites]")!;
          commentBoxFavorites.insertAdjacentText("afterbegin", comment.favourites_count.toString());

          const commentBoxReblogs = commentBox.querySelector<HTMLElementTagNameMap["span"]>("span[data-reblogs]")!;
          commentBoxReblogs.insertAdjacentText("afterbegin", comment.reblogs_count.toString());

          if (comment.in_reply_to_id === post.postId || !(comment.in_reply_to_id in commentMap)) {
            commentMap[comment.id] = commentsList.length;
            commentsList.push(commentBox);
          } else if (comment.in_reply_to_id in commentMap) {
            const commentsIndex = commentMap[comment.in_reply_to_id]!;
            commentMap[comment.id] = commentsIndex;
            const parentThreadDiv =
              commentsList[commentsIndex]!.querySelector<HTMLElementTagNameMap["div"]>("div[data-comment-thread]")!;
            parentThreadDiv.setAttribute("aria-hidden", "false");
            parentThreadDiv.appendChild(commentBox);
          }
        });
        window.dispatchEvent(
          new CustomEvent("mastodon-comments", {
            detail: {
              comments: commentsList.map((fragment) => {
                const div = document.createElement("div");
                div.appendChild(fragment);
                return div.innerHTML;
              }),
            },
          }),
        );
        console.log(commentsList);
      } catch (e) {
        console.error("Fetch Mastodon comments error", e);
        window.dispatchEvent(new CustomEvent("mastodon-comments-error", { detail: { error: e } }));
      }
    }

    document.addEventListener("retrieveComments", (e) => {
      const { link, instance, user, postId, blacklistedComments, selfUrl } = (e as RetrieveCommentsEvent).detail;
      renderComments({ link, instance, user, postId }, new Set(blacklistedComments), selfUrl);
    });
  }
</script>
