---
import { type CollectionEntry, getCollection, render } from "astro:content";
import PostLayout from "@/layouts/PostLayout.astro";
import Header from "@/components/Header.astro";
import Footer from "@/components/Footer.astro";
import Datetime from "@/components/Datetime.astro";
import Tag from "@/components/Tag.astro";
import { getPostSlug, getPostUrl } from "@/utils/getPostPaths";
import { getSortedPosts } from "@/utils/getSortedPosts";
import { slugifyStr } from "@/utils/slugify";
import { toTransitionName } from "@/utils/toTransitionName";
import EditPost from "./_components/EditPost.astro";
import ShareLinks from "./_components/ShareLinks.astro";
import BackButton from "./_components/BackButton.astro";
import BackToTopButton from "./_components/BackToTopButton.astro";
import AdjacentPostNav from "./_components/AdjacentPostNav.astro";
import config from "@/config";

export async function getStaticPaths() {
  const posts = await getCollection("posts");
  const sortedPosts = getSortedPosts(posts);

  return sortedPosts.map((post, index) => ({
    params: { slug: getPostSlug(post.id, post.filePath) },
    props: {
      post,
      prevPost:
        index > 0
          ? {
              id: sortedPosts[index - 1].id,
              title: sortedPosts[index - 1].data.title,
              filePath: sortedPosts[index - 1].filePath,
            }
          : null,
      nextPost:
        index < sortedPosts.length - 1
          ? {
              id: sortedPosts[index + 1].id,
              title: sortedPosts[index + 1].data.title,
              filePath: sortedPosts[index + 1].filePath,
            }
          : null,
    },
  }));
}

type AdjacentPost = {
  id: string;
  title: string;
  filePath: string | undefined;
} | null;

type Props = {
  post: CollectionEntry<"posts">;
  prevPost: AdjacentPost;
  nextPost: AdjacentPost;
};

const { post, prevPost, nextPost } = Astro.props;
const locale = Astro.currentLocale ?? config.site.lang;

const {
  title,
  description,
  ogImage: initOgImage,
  canonicalURL,
  pubDatetime,
  modDatetime,
  timezone,
  tags,
  hideEditPost,
} = post.data;

const { Content } = await render(post);

let ogImageUrl: string | undefined;

if (typeof initOgImage === "string") {
  ogImageUrl = initOgImage;
} else if (initOgImage?.src) {
  ogImageUrl = initOgImage.src;
}

if (!ogImageUrl && config.features.dynamicOgImage) {
  const postUrl = getPostUrl(post.id, post.filePath, locale).replace(
    /\/+$/,
    ""
  );
  ogImageUrl = `${postUrl}/index.png`;
}

const ogImage = ogImageUrl
  ? new URL(ogImageUrl, Astro.url.origin).href
  : undefined;
---

<PostLayout
  title={`${title} | ${config.site.title}`}
  description={description}
  ogImage={ogImage}
  canonicalURL={canonicalURL}
  pubDatetime={pubDatetime}
  modDatetime={modDatetime}
>
  <Header />

  <BackButton />

  <main
    id="main-content"
    class:list={["app-layout", { "mt-8": !config.features.showBackButton }]}
    data-pagefind-body
  >
    <h1
      style={{ viewTransitionName: toTransitionName(title) }}
      class="text-accent inline-block text-2xl font-bold sm:text-3xl"
    >
      {title}
    </h1>

    <div class="my-2 flex items-center gap-2">
      <Datetime {pubDatetime} {modDatetime} {timezone} size="lg" />
      <span
        aria-hidden="true"
        class:list={[
          "text-muted-foreground max-sm:hidden",
          { hidden: !config.features.editPost?.enabled || hideEditPost },
        ]}
      >
        |
      </span>
      <EditPost {hideEditPost} {post} class="max-sm:hidden" />
    </div>

    <article
      id="article"
      class:list={[
        "mt-8 w-full",
        "app-prose max-w-app",
        "prose-pre:bg-(--shiki-light-bg) dark:prose-pre:bg-(--shiki-dark-bg)",
      ]}
    >
      <Content />
    </article>

    <hr class="my-8 border-dashed" />

    <EditPost class="sm:hidden" {hideEditPost} {post} />

    <BackToTopButton />

    <ul class="mt-4 mb-8 flex flex-wrap gap-4 sm:my-8">
      {tags.map(tag => <Tag tag={slugifyStr(tag)} tagName={tag} size="sm" />)}
    </ul>

    <ShareLinks />

    <hr class="my-8 border-dashed" />

    <AdjacentPostNav {prevPost} {nextPost} />
  </main>
  <Footer />
</PostLayout>

<script is:inline data-astro-rerun>
  function createProgressBar() {
    const progressContainer = document.createElement("div");
    progressContainer.className =
      "progress-container fixed top-0 z-10 h-1 w-full bg-background";

    const progressBar = document.createElement("div");
    progressBar.className = "progress-bar h-1 w-0 bg-accent";
    progressBar.id = "myBar";

    progressContainer.appendChild(progressBar);
    document.body.appendChild(progressContainer);
  }
  createProgressBar();

  function updateScrollProgress() {
    document.addEventListener("scroll", () => {
      const winScroll =
        document.body.scrollTop || document.documentElement.scrollTop;
      const height =
        document.documentElement.scrollHeight -
        document.documentElement.clientHeight;
      const scrolled = (winScroll / height) * 100;
      if (document) {
        const myBar = document.getElementById("myBar");
        if (myBar) {
          myBar.style.width = scrolled + "%";
        }
      }
    });
  }
  updateScrollProgress();

  function addHeadingLinks() {
    const headings = Array.from(
      document.querySelectorAll("h2, h3, h4, h5, h6")
    );
    for (const heading of headings) {
      heading.classList.add("group");
      const link = document.createElement("a");
      link.className =
        "heading-link ms-2 no-underline opacity-75 md:opacity-0 md:group-hover:opacity-100 md:focus:opacity-100";
      link.href = "#" + heading.id;

      const span = document.createElement("span");
      span.ariaHidden = "true";
      span.innerText = "#";
      link.appendChild(span);
      heading.appendChild(link);
    }
  }
  addHeadingLinks();

  function attachCopyButtons() {
    const copyButtonLabel = "Copy";
    const codeBlocks = Array.from(document.querySelectorAll("pre"));

    for (const codeBlock of codeBlocks) {
      const wrapper = document.createElement("div");
      wrapper.style.position = "relative";

      const computedStyle = getComputedStyle(codeBlock);
      const hasFileNameOffset =
        computedStyle.getPropertyValue("--file-name-offset").trim() !== "";

      const topClass = hasFileNameOffset
        ? "top-(--file-name-offset)"
        : "-top-3";

      const copyButton = document.createElement("button");
      copyButton.className = `copy-code absolute end-3 ${topClass} rounded bg-muted border border-muted px-2 py-1 text-xs leading-4 text-foreground font-medium`;
      copyButton.innerHTML = copyButtonLabel;
      codeBlock.setAttribute("tabindex", "0");
      codeBlock.appendChild(copyButton);

      codeBlock?.parentNode?.insertBefore(wrapper, codeBlock);
      wrapper.appendChild(codeBlock);

      copyButton.addEventListener("click", async () => {
        await copyCode(codeBlock, copyButton);
      });
    }

    async function copyCode(block, button) {
      const code = block.querySelector("code");
      const text = code?.innerText;

      await navigator.clipboard.writeText(text ?? "");

      button.innerText = "Copied";

      setTimeout(() => {
        button.innerText = copyButtonLabel;
      }, 700);
    }
  }
  attachCopyButtons();

  /** Accessible image lightbox for article images. */
  function initLightbox() {
    const article = document.getElementById("article");
    if (!article) return;

    const prefersReducedMotion = () =>
      window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    let overlay = null;
    let lastFocused = null;

    // Defer attribute mutations so they don't push the LCP timestamp.
    // Event listeners below use delegation and don't need the attributes to exist yet.
    requestAnimationFrame(() => {
      const images = Array.from(article.querySelectorAll("img"));
      for (const image of images) {
        if (image.closest("a")) continue;
        image.setAttribute("role", "button");
        image.setAttribute("tabindex", "0");
        image.setAttribute("aria-haspopup", "dialog");
        image.setAttribute(
          "aria-label",
          image.alt ? `Zoom image: ${image.alt}` : "Zoom image"
        );
      }
    });

    function open(src, alt, trigger) {
      if (overlay) return;
      lastFocused = trigger ?? document.activeElement;

      overlay = document.createElement("div");
      overlay.setAttribute("role", "dialog");
      overlay.setAttribute("aria-modal", "true");
      overlay.setAttribute(
        "aria-label",
        alt ? `Image preview: ${alt}` : "Image preview"
      );
      overlay.className =
        "fixed inset-0 z-50 flex cursor-zoom-out items-center justify-center bg-black/70 backdrop-blur-sm opacity-0 transition-opacity duration-200 motion-reduce:transition-none";

      const closeButton = document.createElement("button");
      closeButton.type = "button";
      closeButton.setAttribute("aria-label", "Close image preview");
      closeButton.className =
        "absolute end-4 top-4 rounded p-2 text-3xl leading-none text-white";
      closeButton.innerHTML = "&#10005;";
      closeButton.addEventListener("click", close);

      const image = document.createElement("img");
      image.src = src;
      image.alt = "";
      image.className =
        "max-h-[90dvh] max-w-[90dvw] cursor-default object-contain";

      overlay.append(closeButton, image);
      overlay.addEventListener("click", e => {
        if (e.target === overlay && currentScale <= 1) close();
      });

      let currentScale = 1;
      let translateX = 0;
      let translateY = 0;
      let initialDist = 0;
      let initialScale = 1;
      let panStartX = 0;
      let panStartY = 0;
      let panStartTranslateX = 0;
      let panStartTranslateY = 0;
      let lastTapTime = 0;

      function applyTransform() {
        image.style.transform = `scale(${currentScale}) translate(${translateX}px, ${translateY}px)`;
      }

      function resetTransform() {
        currentScale = 1;
        translateX = 0;
        translateY = 0;
        image.style.transform = "";
      }

      overlay.addEventListener(
        "touchstart",
        e => {
          const t = e.touches;
          if (t.length === 2) {
            initialDist = Math.hypot(
              t[1].clientX - t[0].clientX,
              t[1].clientY - t[0].clientY
            );
            initialScale = currentScale;
          } else if (t.length === 1) {
            const now = Date.now();
            if (now - lastTapTime < 300) {
              e.preventDefault();
              if (currentScale > 1) {
                resetTransform();
              } else {
                currentScale = 2;
                translateX = 0;
                translateY = 0;
                applyTransform();
              }
              lastTapTime = 0;
              panStartX = t[0].clientX;
              panStartY = t[0].clientY;
              panStartTranslateX = translateX;
              panStartTranslateY = translateY;
            } else {
              lastTapTime = now;
              if (currentScale > 1) {
                panStartX = t[0].clientX;
                panStartY = t[0].clientY;
                panStartTranslateX = translateX;
                panStartTranslateY = translateY;
              }
            }
          }
        },
        { passive: false }
      );

      overlay.addEventListener(
        "touchmove",
        e => {
          const t = e.touches;
          if (t.length === 2) {
            e.preventDefault();
            const dist = Math.hypot(
              t[1].clientX - t[0].clientX,
              t[1].clientY - t[0].clientY
            );
            currentScale = Math.min(
              4,
              Math.max(1, initialScale * (dist / initialDist))
            );
            applyTransform();
          } else if (t.length === 1) {
            if (currentScale > 1) {
              e.preventDefault();
              translateX =
                panStartTranslateX + (t[0].clientX - panStartX) / currentScale;
              translateY =
                panStartTranslateY + (t[0].clientY - panStartY) / currentScale;
              const maxX = Math.max(
                0,
                (image.clientWidth - overlay.clientWidth / currentScale) / 2
              );
              const maxY = Math.max(
                0,
                (image.clientHeight - overlay.clientHeight / currentScale) / 2
              );
              translateX = Math.min(maxX, Math.max(-maxX, translateX));
              translateY = Math.min(maxY, Math.max(-maxY, translateY));
              applyTransform();
            } else {
              e.preventDefault();
            }
          }
        },
        { passive: false }
      );

      overlay.addEventListener("touchend", e => {
        if (e.touches.length === 0 && currentScale <= 1.05) {
          resetTransform();
        }
      });

      overlay.addEventListener("touchcancel", e => {
        if (e.touches.length === 0 && currentScale <= 1.05) {
          resetTransform();
        }
      });

      document.body.appendChild(overlay);
      document.body.style.overflow = "hidden";
      document.addEventListener("keydown", onKeyDown);
      window.__closeLightbox = close;

      requestAnimationFrame(() => overlay?.classList.add("opacity-100"));
      closeButton.focus();
    }

    function close() {
      if (!overlay) return;
      const el = overlay;
      overlay = null;
      window.__closeLightbox = null;

      document.removeEventListener("keydown", onKeyDown);
      document.body.style.overflow = "";
      lastFocused?.focus();
      lastFocused = null;

      if (prefersReducedMotion()) {
        el.remove();
        return;
      }
      const remove = () => el.remove();
      el.addEventListener("transitionend", remove, { once: true });
      setTimeout(remove, 250); // fallback in case transitionend never fires
      el.classList.remove("opacity-100");
    }

    function onKeyDown(e) {
      if (e.key === "Escape") {
        close();
      } else if (e.key === "Tab") {
        trapFocus(e);
      }
    }

    // Keep keyboard focus inside the open dialog.
    function trapFocus(e) {
      if (!overlay) return;
      const focusables = overlay.querySelectorAll(
        'a[href], button, [tabindex]:not([tabindex="-1"])'
      );
      if (focusables.length === 0) return;
      const first = focusables[0];
      const last = focusables[focusables.length - 1];
      if (e.shiftKey && document.activeElement === first) {
        e.preventDefault();
        last.focus();
      } else if (!e.shiftKey && document.activeElement === last) {
        e.preventDefault();
        first.focus();
      }
    }

    function triggerFromEvent(e) {
      const image = e.target.closest("img");
      if (!image || !article.contains(image) || image.closest("a")) return null;
      return image;
    }

    function activate(image) {
      open(image.currentSrc || image.src, image.alt, image);
    }

    article.addEventListener("click", e => {
      const image = triggerFromEvent(e);
      if (!image) return;
      e.preventDefault();
      activate(image);
    });

    article.addEventListener("keydown", e => {
      if (e.key !== "Enter" && e.key !== " " && e.key !== "Spacebar") return;
      const image = triggerFromEvent(e);
      if (!image) return;
      e.preventDefault();
      activate(image);
    });
  }
  initLightbox();

  if (!window.__lightboxSwapBound) {
    window.__lightboxSwapBound = true;
    document.addEventListener("astro:before-swap", () =>
      window.__closeLightbox?.()
    );
  }

  document.addEventListener("astro:after-swap", () =>
    window.scrollTo({ left: 0, top: 0, behavior: "instant" })
  );
</script>
