import type { MDXComponents } from "mdx/types";
import Image from "next/image";
import {
  type AnchorHTMLAttributes,
  type HTMLAttributes,
  isValidElement,
  type ReactNode,
} from "react";
import { Link } from "@/i18n/navigation";
import { slugifyHeading } from "@/lib/blog/toc";

/** Flattens MDX heading children to plain text so the id matches the TOC slug. */
function headingText(node: ReactNode): string {
  if (node == null || typeof node === "boolean") {
    return "";
  }
  if (typeof node === "string" || typeof node === "number") {
    return String(node);
  }
  if (Array.isArray(node)) {
    return node.map(headingText).join("");
  }
  if (isValidElement(node)) {
    return headingText((node.props as { children?: ReactNode }).children);
  }
  return "";
}

function Heading({ children, ...p }: HTMLAttributes<HTMLHeadingElement>) {
  const id = slugifyHeading(headingText(children));
  // `scroll-mt` compense le header fixe quand la TOC saute a l'ancre.
  return (
    <h2 id={id} className="scroll-mt-[120px]" {...p}>
      {children}
    </h2>
  );
}

/**
 * Seuls les elements qui apportent un COMPORTEMENT sont surcharges : ancres de
 * titres (id derive du slug de la TOC), routage localise des liens internes,
 * et `next/image` pour les illustrations. Toute la mise en forme du flux
 * (paragraphes, listes, citations, `strong`, `em`, `hr`, `code`) vient de
 * l'utility `prose` posee sur le conteneur dans `PostLayout.tsx`.
 */
export const mdxComponents: MDXComponents = {
  // Bodies start at `##`; a stray `#` is downgraded to keep a single page <h1>.
  h1: Heading,
  h2: Heading,
  // Pas de `h3` : la TOC n'indexe que les `##` (voir `extractHeadings`), donc
  // un `<h3>` n'a ni ancre ni comportement propre — `prose` suffit.
  a: ({ href = "", children, ...rest }: AnchorHTMLAttributes<HTMLAnchorElement>) => {
    const isInternal = href.startsWith("/");
    if (isInternal) {
      // Internal MDX links are rare; cast to satisfy typed pathnames.
      return <Link href={href as never}>{children}</Link>;
    }
    return (
      <a href={href} target="_blank" rel="noopener noreferrer" {...rest}>
        {children}
      </a>
    );
  },
  // `not-prose` protege le positionnement `fill` : les marges que `prose`
  // poserait sur le <img> decaleraient une image en `position: absolute`.
  img: ({ src = "", alt = "" }: { src?: string; alt?: string }) => (
    <span className="not-prose relative mt-8 block aspect-16/9 w-full overflow-hidden border border-hairline">
      <Image
        src={src}
        alt={alt}
        fill
        sizes="(min-width: 768px) 680px, 100vw"
        className="object-cover"
      />
    </span>
  ),
};
