/**
 * Page builder blocks: the sections an editor can compose in the admin panel.
 *
 * The catalogue lives in App\Services\Content\BlockDefinitions (one definition per
 * type, which also drives the editor and the validator); this file has the matching
 * React component per type and renders them in the data's own order. Text fields
 * arrive as { nl, en } and fall back to Dutch, just like every other text on the
 * site.
 */
import { Link } from "@tanstack/react-router";
import {
  Award,
  BookOpen,
  Clock,
  CreditCard,
  FlaskConical,
  HeartPulse,
  Leaf,
  Microscope,
  Package,
  ShieldCheck,
  Sparkles,
  Star,
  Truck,
  Users,
  type LucideIcon,
} from "lucide-react";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import ProductCard from "@/components/ProductCard";
import HomeReviews from "@/components/HomeReviews";
import { useCategories, useProducts } from "@/hooks/useWooCommerce";
import { useT, useLang, type Lang } from "@/lib/i18n";
import { sanitizedHtml } from "@/lib/sanitize";
import type { EditorialBlock } from "@/lib/content.functions";

type Data = Record<string, unknown>;

/** A translated text field: the current language, Dutch as the fallback. */
function pick(value: unknown, lang: Lang): string {
  if (typeof value === "string") return value;
  if (!value || typeof value !== "object") return "";

  const pair = value as Record<string, unknown>;
  const wanted = typeof pair[lang] === "string" ? (pair[lang] as string) : "";

  return wanted || (typeof pair.nl === "string" ? pair.nl : "");
}

function rows(value: unknown): Data[] {
  return Array.isArray(value) ? value.filter((row): row is Data => !!row && typeof row === "object") : [];
}

type BlockImage = { url: string; alt_nl: string; alt_en: string; width: number | null; height: number | null };

function blockImage(value: unknown): BlockImage | null {
  if (!value || typeof value !== "object") return null;

  const image = value as Record<string, unknown>;
  const url = typeof image.url === "string" ? image.url : "";

  return url === ""
    ? null
    : {
        url,
        alt_nl: typeof image.alt_nl === "string" ? image.alt_nl : "",
        alt_en: typeof image.alt_en === "string" ? image.alt_en : "",
        width: typeof image.width === "number" ? image.width : null,
        height: typeof image.height === "number" ? image.height : null,
      };
}

const ALT: Partial<Record<string, string>> = {
  "shield-check": "ShieldCheck",
  "flask-conical": "FlaskConical",
  microscope: "Microscope",
  truck: "Truck",
  package: "Package",
  clock: "Clock",
  "credit-card": "CreditCard",
  sparkles: "Sparkles",
  star: "Star",
  award: "Award",
  "book-open": "BookOpen",
  users: "Users",
  "heart-pulse": "HeartPulse",
  leaf: "Leaf",
};

const ICONS: Record<string, LucideIcon> = {
  ShieldCheck,
  FlaskConical,
  Microscope,
  Truck,
  Package,
  Clock,
  CreditCard,
  Sparkles,
  Star,
  Award,
  BookOpen,
  Users,
  HeartPulse,
  Leaf,
};

function iconFor(name: unknown): LucideIcon {
  const key = typeof name === "string" ? ALT[name] : undefined;

  return key ? (ICONS[key] ?? Sparkles) : Sparkles;
}

/** The eyebrow + heading + intro that most sections share. */
function SectionHead({ eyebrow, heading, intro }: { eyebrow?: string; heading?: string; intro?: string }) {
  if (!heading && !intro) return null;

  return (
    <div className="mb-10 max-w-2xl">
      {eyebrow && <p className="text-primary text-xs font-bold uppercase tracking-[0.24em] mb-2">{eyebrow}</p>}
      {heading && <h2 className="font-display text-3xl md:text-4xl uppercase tracking-tighter mb-3">{heading}</h2>}
      {intro && <p className="text-sm md:text-base text-muted-foreground leading-relaxed">{intro}</p>}
    </div>
  );
}

/** Shared button styling: the storefront's gold call to action. */
function ButtonLink({ url, children, tone = "gold" }: { url: string; children: React.ReactNode; tone?: "gold" | "outline" }) {
  const classes =
    tone === "gold"
      ? "group relative inline-flex justify-center items-center gap-2.5 px-8 py-3.5 bg-gradient-to-br from-[#FFD777] to-[#936B25] text-black font-display font-bold uppercase tracking-[0.18em] text-xs hover:brightness-110 transition-all duration-300"
      : "inline-flex justify-center items-center gap-2.5 px-8 py-3.5 border border-border text-foreground font-display font-bold uppercase tracking-[0.18em] text-xs hover:border-primary hover:text-primary transition-all duration-300";

  // Internal paths keep client-side routing; external urls stay a plain anchor.
  return url.startsWith("/") ? (
    <Link to={url} className={classes}>
      {children}
    </Link>
  ) : (
    <a href={url} className={classes} rel="noopener">
      {children}
    </a>
  );
}

function Buttons({
  items,
  lang,
  className = "",
  tone,
}: {
  items: Data[];
  lang: Lang;
  className?: string;
  tone?: "gold" | "outline";
}) {
  if (items.length === 0) return null;

  return (
    <div className={`flex flex-wrap gap-3 ${className}`}>
      {items.map((button, index) => {
        const label = pick(button.label, lang);
        const url = typeof button.url === "string" ? button.url : "";

        if (label === "" || url === "") return null;

        return (
          <ButtonLink key={index} url={url} tone={tone ?? (index === 0 ? "gold" : "outline")}>
            {label}
          </ButtonLink>
        );
      })}
    </div>
  );
}

// --- Blocks ----------------------------------------------------------------

function Hero({ data }: { data: Data }) {
  const lang = useLang();
  const image = blockImage(data.image);

  return (
    <section className="relative bg-[#050505] text-white overflow-hidden">
      <div className="container relative py-14 lg:py-20 grid gap-10 lg:grid-cols-2 lg:items-center">
        <div>
          <h1 className="font-display text-4xl md:text-5xl lg:text-6xl font-extrabold leading-[0.95] tracking-tight uppercase mb-5">
            {pick(data.heading, lang)}
          </h1>
          {pick(data.subheading, lang) !== "" && (
            <p className="text-white/70 text-base md:text-lg leading-relaxed mb-8 max-w-xl">
              {pick(data.subheading, lang)}
            </p>
          )}
          <Buttons items={rows(data.buttons)} lang={lang} />
        </div>

        {image && (
          <img
            src={image.url}
            alt={lang === "en" ? image.alt_en || image.alt_nl : image.alt_nl || image.alt_en}
            width={image.width ?? 1200}
            height={image.height ?? 900}
            loading="eager"
            decoding="async"
            className="w-full rounded-2xl object-cover aspect-[4/3]"
          />
        )}
      </div>
    </section>
  );
}

function TextBlock({ data }: { data: Data }) {
  const lang = useLang();
  const body = pick(data.body, lang);

  if (body === "") return null;

  return (
    <section className="container py-12 md:py-16 max-w-3xl">
      <div
        className="prose prose-lg max-w-none prose-headings:font-bold prose-headings:leading-snug prose-h2:text-xl md:prose-h2:text-2xl prose-h2:mt-10 prose-h3:text-lg md:prose-h3:text-xl"
        dangerouslySetInnerHTML={sanitizedHtml(body)}
      />
    </section>
  );
}

function TextImage({ data }: { data: Data }) {
  const lang = useLang();
  const image = blockImage(data.image);
  const body = pick(data.body, lang);
  const side = data.image_side === "left" ? "left" : "right";

  return (
    <section className="container py-12 md:py-16">
      <div className="grid gap-8 md:gap-12 md:grid-cols-2 md:items-center">
        <div className={side === "left" ? "md:order-2" : ""}>
          {pick(data.heading, lang) !== "" && (
            <h2 className="font-display text-3xl md:text-4xl uppercase tracking-tighter mb-4">{pick(data.heading, lang)}</h2>
          )}
          {body !== "" && (
            <div
              className="prose max-w-none prose-headings:font-bold prose-h2:text-xl prose-h3:text-lg"
              dangerouslySetInnerHTML={sanitizedHtml(body)}
            />
          )}
          {typeof data.button_url === "string" && data.button_url !== "" && pick(data.button_label, lang) !== "" && (
            <Buttons items={[{ label: data.button_label, url: data.button_url }]} lang={lang} className="mt-6" />
          )}
        </div>

        {image && (
          <img
            src={image.url}
            alt={lang === "en" ? image.alt_en || image.alt_nl : image.alt_nl || image.alt_en}
            width={image.width ?? 1000}
            height={image.height ?? 800}
            loading="lazy"
            decoding="async"
            className={`w-full rounded-2xl object-cover aspect-[5/4] ${side === "left" ? "md:order-1" : ""}`}
          />
        )}
      </div>
    </section>
  );
}

function Features({ data }: { data: Data }) {
  const t = useT();
  const lang = useLang();
  const items = rows(data.items);

  if (items.length === 0 && pick(data.heading, lang) === "") return null;

  return (
    <section className="border-y border-border/60 bg-card/40">
      <div className="container py-14 md:py-16">
        <SectionHead eyebrow={t("Waarom Peptidewinkel", "Why Peptidewinkel")} heading={pick(data.heading, lang)} intro={pick(data.intro, lang)} />
        <div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-4">
          {items.map((item, index) => {
            const Icon = iconFor(item.icon);

            return (
              <div key={index} className="rounded-2xl border border-border/60 bg-card p-5 shadow-card">
                <Icon className="w-5 h-5 text-primary mb-3" aria-hidden="true" />
                <h3 className="font-semibold text-foreground mb-1.5">{pick(item.title, lang)}</h3>
                <p className="text-sm text-muted-foreground leading-relaxed">{pick(item.text, lang)}</p>
              </div>
            );
          })}
        </div>
      </div>
    </section>
  );
}

function Faq({ data }: { data: Data }) {
  const lang = useLang();
  const items = rows(data.items);

  if (items.length === 0) return null;

  return (
    <section className="container py-14 md:py-16 max-w-3xl">
      <SectionHead heading={pick(data.heading, lang)} />
      <Accordion type="single" collapsible className="space-y-2">
        {items.map((item, index) => (
          <AccordionItem
            key={index}
            value={`block-faq-${index}`}
            className="border border-border rounded-md px-5 bg-card hover:border-primary/40 transition-colors"
          >
            <AccordionTrigger className="text-sm font-medium text-left hover:no-underline">
              {pick(item.question, lang)}
            </AccordionTrigger>
            <AccordionContent className="text-sm text-muted-foreground leading-relaxed whitespace-pre-line">
              {pick(item.answer, lang)}
            </AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </section>
  );
}

function Cta({ data }: { data: Data }) {
  const lang = useLang();
  const image = blockImage(data.image);
  const label = pick(data.button_label, lang);
  const url = typeof data.button_url === "string" ? data.button_url : "";

  return (
    <section className="relative overflow-hidden bg-shell text-shell-foreground">
      {image && (
        <>
          <img src={image.url} alt="" aria-hidden="true" className="absolute inset-0 h-full w-full object-cover opacity-25" />
          <div className="absolute inset-0 bg-gradient-to-r from-shell via-shell/85 to-shell/40" aria-hidden="true" />
        </>
      )}

      <div className="container relative py-14 md:py-16 max-w-3xl text-center">
        {pick(data.heading, lang) !== "" && (
          <h2 className="font-display text-3xl md:text-4xl uppercase tracking-tighter mb-4">{pick(data.heading, lang)}</h2>
        )}
        {pick(data.text, lang) !== "" && <p className="text-shell-foreground/70 leading-relaxed mb-7">{pick(data.text, lang)}</p>}
        {label !== "" && url !== "" && (
          <div className="flex justify-center">
            <Buttons items={[{ label: data.button_label, url }]} lang={lang} />
          </div>
        )}
      </div>
    </section>
  );
}

function Gallery({ data }: { data: Data }) {
  const lang = useLang();
  const items = rows(data.images)
    .map((row) => ({ image: blockImage(row.image), caption: pick(row.caption, lang) }))
    .filter((row): row is { image: BlockImage; caption: string } => row.image !== null);

  if (items.length === 0) return null;

  return (
    <section className="container py-14 md:py-16">
      <SectionHead heading={pick(data.heading, lang)} />
      <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
        {items.map((item, index) => (
          <figure key={index} className="overflow-hidden rounded-2xl border border-border/60 bg-card">
            <img
              src={item.image.url}
              alt={lang === "en" ? item.image.alt_en || item.image.alt_nl : item.image.alt_nl || item.image.alt_en}
              width={item.image.width ?? 800}
              height={item.image.height ?? 600}
              loading="lazy"
              decoding="async"
              className="aspect-[4/3] w-full object-cover"
            />
            {item.caption !== "" && (
              <figcaption className="px-4 py-3 text-xs text-muted-foreground">{item.caption}</figcaption>
            )}
          </figure>
        ))}
      </div>
    </section>
  );
}

function Products({ data }: { data: Data }) {
  const t = useT();
  const lang = useLang();
  const slug = typeof data.category === "string" ? data.category : "";
  const limit = typeof data.limit === "number" ? Math.min(12, Math.max(1, data.limit)) : 4;

  const { data: categories } = useCategories();
  const categoryId = slug === "" ? undefined : (categories ?? []).find((category) => category.slug === slug)?.id;
  const { data: productData, isLoading } = useProducts({ per_page: limit, category: categoryId });
  const products = (productData?.products ?? []).slice(0, limit);

  if (!isLoading && products.length === 0) return null;

  return (
    <section className="container py-14 md:py-16">
      <SectionHead heading={pick(data.heading, lang)} />
      <div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-4">
        {products.map((product) => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
      <div className="mt-8">
        <Buttons items={[{ label: { nl: t("Alle producten", "All products"), en: t("Alle producten", "All products") }, url: "/shop" }]} lang={lang} tone="outline" />
      </div>
    </section>
  );
}

function Reviews({ data }: { data: Data }) {
  const lang = useLang();

  return <HomeReviews heading={pick(data.heading, lang) || undefined} />;
}

function Categories({ data }: { data: Data }) {
  const t = useT();
  const lang = useLang();
  const limit = typeof data.limit === "number" ? Math.min(12, Math.max(1, data.limit)) : 6;

  const { data: categories } = useCategories();
  // Only the top level: sub-categories belong on their parent's page.
  const list = (categories ?? []).filter((category) => category.parent === 0).slice(0, limit);

  if (list.length === 0) return null;

  return (
    <section className="container py-14 md:py-16">
      <SectionHead heading={pick(data.heading, lang)} intro={pick(data.intro, lang)} />
      <div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
        {list.map((category) => (
          <Link
            key={category.id}
            to="/product-category/$slug"
            params={{ slug: category.slug }}
            className="group flex items-center gap-4 rounded-2xl border border-border/60 bg-card p-4 transition-all duration-300 hover:-translate-y-0.5 hover:border-brand-gold/40"
          >
            <img
              src={category.image}
              alt=""
              width={64}
              height={64}
              loading="lazy"
              decoding="async"
              className="h-14 w-14 shrink-0 rounded-xl object-cover"
            />
            <span className="min-w-0">
              <span className="block truncate font-display font-semibold group-hover:text-brand-gold transition-colors">
                {category.name}
              </span>
              <span className="block text-xs text-muted-foreground">
                {category.count} {category.count === 1 ? t("product", "product") : t("producten", "products")}
              </span>
            </span>
          </Link>
        ))}
      </div>
      <div className="mt-8">
        <Buttons
          items={[{ label: { nl: t("Alle categorieën", "All categories"), en: t("Alle categorieën", "All categories") }, url: "/shop" }]}
          lang={lang}
          tone="outline"
        />
      </div>
    </section>
  );
}

// --- Registry --------------------------------------------------------------

const BLOCKS: Record<string, (props: { data: Data }) => React.ReactElement | null> = {
  hero: Hero,
  text: TextBlock,
  text_image: TextImage,
  features: Features,
  faq: Faq,
  cta: Cta,
  gallery: Gallery,
  products: Products,
  reviews: Reviews,
  categories: Categories,
};

/** Render a page built in the admin panel, in the editor's own order. */
export default function PageBlocks({ blocks }: { blocks: EditorialBlock[] }) {
  return (
    <>
      {blocks.map((block, index) => {
        const Component = BLOCKS[block.type];

        if (!Component) return null;

        return <Component key={`${block.type}-${index}`} data={block.data as Data} />;
      })}
    </>
  );
}
