import type { ReactNode } from "react";

import type { Section } from "@/lib/api";
import type { RenderContext } from "@/lib/i18n";

import MediaImage from "../ui/MediaImage";
import RichText from "../ui/RichText";
import ProjectCard from "../ui/ProjectCard";
import ArticleCard from "../ui/ArticleCard";
import Btn from "../ui/Btn";
import SectionHeader from "../ui/SectionHeader";
import Accordion from "../ui/Accordion";
import Carousel from "../ui/Carousel";
import VideoEmbed from "../ui/VideoEmbed";
import Gallery from "../ui/Gallery";
import LeadForm from "../LeadForm";
import HeroSearch from "../HeroSearch";

/**
 * The 24 section blocks.
 *
 * These are the real page-building system — `resources/views/sections/` had no
 * parallel "preview" set, and neither does this. Props arrive fully resolved
 * from the API: images carry srcset and dimensions, dynamic blocks carry their
 * expanded records, URLs are finished, rich text is pre-processed. Every
 * component here is presentational.
 *
 * **Class names and nesting match the Blade templates exactly**, because
 * site.css was ported verbatim. A renamed class or an extra wrapper is a broken
 * layout that will present as a CSS bug — several of the comments below record
 * exactly that having happened once already.
 */

/**
 * The strings a LeadForm needs, resolved on the server.
 *
 * Client Components cannot receive a function across the RSC boundary, so the
 * translator is applied here and plain strings are handed over.
 */
export function formLabels(t: RenderContext["t"]): Record<string, string> {
  return Object.fromEntries(
    [
      "js.form.sent",
      "js.form.invalid",
      "forms.name",
      "forms.email",
      "forms.phone",
      "forms.mobile",
      "forms.full_name",
      "forms.message",
      "forms.send",
      "forms.send_message",
      "forms.project",
      "forms.choose_project",
    ].map((key) => [key, t(key)]),
  );
}

type Props = Record<string, any>;

type SectionFn = (props: Props, ctx: RenderContext) => ReactNode;

const SECTIONS: Record<string, SectionFn> = {
  /*
   * The home hero.
   *
   * preload="none" and the data-src source are deliberate: left at the browser
   * default the video pulled 2.8 MB concurrently with the poster it is supposed
   * to be hidden behind, taken straight from the largest paint. The poster holds
   * the frame and a script starts the download after load. <noscript> carries a
   * plain autoplaying video so the hero is not blank without JavaScript.
   */
  hero_video: (p, ctx) => (
    <section className="home-hero">
      {p.media?.src ? (
        <>
          <video
            className="home-hero__video"
            muted
            loop
            playsInline
            preload="none"
            data-hero-video=""
            poster={p.poster?.src}
            aria-hidden="true"
          >
            <source data-src={p.media.src} type="video/mp4" />
          </video>
          <noscript>
            <video
              className="home-hero__video"
              autoPlay
              muted
              loop
              playsInline
              poster={p.poster?.src}
              aria-hidden="true"
            />
          </noscript>
        </>
      ) : p.poster ? (
        <MediaImage image={p.poster} className="home-hero__video" eager alt="" />
      ) : null}

      <div className="home-hero__inner container">
        {p.heading ? <h1>{p.heading}</h1> : null}

        {/*
          A GET form to the projects listing: every combination it can produce is
          a real, crawlable, bookmarkable URL.
        */}
        {(p.showFilters ?? true) && ctx.filterOptions ? (
          <HeroSearch
            action={ctx.pageUrls.projects ?? "/"}
            options={ctx.filterOptions}
            t={ctx.t}
          />
        ) : null}
      </div>

      <a className="hero-scroll" href="#main-content" aria-hidden="true" tabIndex={-1} />
    </section>
  ),

  feature_grid: (p) => (
    <section id="values" className="feature-grid" aria-label={p.label ?? ""}>
      {(p.items ?? []).map((item: Props, i: number) => (
        <article className="feature-card" key={i}>
          <MediaImage image={item.image} className="feature-image" alt={item.title ?? ""} />
          <span className="feature-number" aria-hidden="true">
            {String(i + 1).padStart(2, "0")}
          </span>
          <h2>
            {item.url ? (
              <a href={item.url}>
                {item.title} <span />
              </a>
            ) : (
              item.title
            )}
          </h2>
        </article>
      ))}
    </section>
  ),

  /*
   * .intro-band IS the two-column grid, with the copy and the artwork as its
   * only two children. Wrapping it in a .container — as an earlier pass did —
   * means the grid never applies, so the artwork is unconstrained and bleeds
   * into the section below.
   *
   * The title is <h1>: on the home page this block carries the page's main
   * heading.
   */
  intro_band: (p) => (
    <section className="intro-band">
      <div className="intro-band__copy">
        {p.title ? <h1>{p.title}</h1> : null}
        <RichText html={p.body} className={undefined} />
        {p.ctaLabel && p.ctaUrl ? (
          <Btn href={p.ctaUrl}>{p.ctaLabel}</Btn>
        ) : null}
      </div>
      {p.image ? (
        <div className="intro-band__visual">
          <MediaImage image={p.image} alt={p.title ?? ""} />
        </div>
      ) : null}
    </section>
  ),

  /*
   * .about-split is the grid and the artwork fills its cell. It used to be a
   * background-image div with role="img", which no browser can offer a srcset
   * for — every visitor downloaded the full-size original. A real img with
   * object-fit renders identically and participates in responsive delivery.
   */
  split_band: (p) => (
    <section className={`about-split${p.reverse ? " about-split--reverse" : ""}`}>
      <div className="about-split__copy">
        {p.title ? <h2 className="band-title">{p.title}</h2> : null}
        <RichText html={p.body} className={undefined} />
      </div>
      {p.image ? (
        <div className="about-split__media">
          <MediaImage image={p.image} alt={p.image.alt || p.title || ""} />
        </div>
      ) : null}
    </section>
  ),

  contact_band: (p, ctx) => (
    <section className="home-contact">
      <div className="home-contact__form">
        {p.title ? <h2 className="band-title">{p.title}</h2> : null}
        <LeadForm
          form={p.formKey ?? "home"}
          layout="home"
          className="contact-form"
          sourceComponent="contact-band"
          labels={formLabels(ctx.t)}
          locale={ctx.locale}
        />
      </div>
      {p.image ? (
        <div className="home-contact__image">
          <MediaImage image={p.image} alt="" />
        </div>
      ) : null}
    </section>
  ),

  /*
   * The design's .about-profile is a centred button on its own, with no
   * container and no heading. Both are supported because the same band is used
   * elsewhere with copy, but neither is required.
   *
   * `new_tab` is opt-in per band: the company profile is a page whose whole
   * content is a flipbook embed, and a reader who opens it has left the site's
   * navigation behind — so that band asks for a tab and the others do not.
   */
  cta_band: (p) => {
    if (!p.title && !(p.ctaLabel && p.ctaUrl)) {
      return null;
    }

    const base = p.variant === "more" ? "about-more" : "about-profile";

    return (
      <section className={`${base}${p.title ? " section--tight" : ""}`}>
        {p.title ? <h2 className="band-title">{p.title}</h2> : null}
        {p.body ? <p>{p.body}</p> : null}
        {p.ctaLabel && p.ctaUrl ? (
          <Btn
            variant="fill"
            href={p.ctaUrl}
            target={p.newTab ? "_blank" : null}
            rel={p.newTab ? "noopener" : null}
          >
            {p.ctaLabel}
          </Btn>
        ) : null}
      </section>
    );
  },

  /*
   * The pair of contact buttons the old site placed inside an article. A block
   * rather than something appended after the body, because position is the whole
   * request — an editor decides where it appears.
   *
   * The numbers come from Settings, so the site has one phone number rather than
   * one per article that someone has to remember to update.
   */
  contact_cta: (p, ctx) => {
    if (!p.telUrl && !p.whatsappUrl) {
      return null;
    }

    return (
      <div className="contact-cta" role="group" aria-label={ctx.t("ui.contact_us_title")}>
        {p.telUrl ? (
          <a className="btn btn--fill contact-cta__phone" href={p.telUrl}>
            {ctx.t("ui.contact_by_phone")}
          </a>
        ) : null}
        {/* WhatsApp opens elsewhere, so it says so; a tel: URL is handled by the
            device, not a new tab, so the phone link does not. */}
        {p.whatsappUrl ? (
          <a
            className="btn btn--fill contact-cta__whatsapp"
            href={p.whatsappUrl}
            target="_blank"
            rel="noopener"
          >
            {ctx.t("ui.contact_by_whatsapp")}
          </a>
        ) : null}
      </div>
    );
  },

  contact_details: (p, ctx) => {
    const address = p.addressStreet || p.addressLocality || "";

    return (
      <section className="section">
        <div className="container contact-page-grid">
          <div className="office-card lined">
            <h2>{p.officeTitle ?? ctx.t("ui.main_office")}</h2>

            {address ? <p>{address}</p> : null}

            <ul className="contact-list">
              {p.phone ? (
                <li>
                  <small>{ctx.t("ui.phone")}</small>
                  <a href={p.telUrl ?? undefined} dir="ltr">
                    {p.phone}
                  </a>
                </li>
              ) : null}

              {p.email ? (
                <li>
                  <small>{ctx.t("ui.email")}</small>
                  <a href={`mailto:${p.email}`} dir="ltr">
                    {p.email}
                  </a>
                </li>
              ) : null}

              {p.openingHours?.length ? (
                <li>
                  <small>{ctx.t("ui.working_hours")}</small>
                  {/* Rendered from the same structured value that feeds
                      OpeningHoursSpecification, so the hours a visitor reads and
                      the hours Google reads cannot diverge. */}
                  <span>
                    {p.openingHours.map((hours: Props, i: number) => {
                      const days = (hours.days ?? []).map((d: string) =>
                        ctx.t(`ui.days.${String(d).toLowerCase()}`),
                      );

                      return (
                        <span key={i}>
                          {days.length ? (
                            <>
                              {ctx.t("ui.from_to", { from: days[0], to: days[days.length - 1] })}
                              <br />
                            </>
                          ) : null}
                          {hours.opens && hours.closes
                            ? ctx.t("ui.hours_range", { opens: hours.opens, closes: hours.closes })
                            : null}
                        </span>
                      );
                    })}
                  </span>
                </li>
              ) : null}
            </ul>
          </div>

          <div>
            <h2 className="section-title">{p.formTitle ?? ctx.t("ui.contact_us_title")}</h2>
            <LeadForm
              form="contact"
              className="form-grid"
              sourceComponent="contact-page"
              showProjectSelect
              projects={p.projects ?? []}
              labels={formLabels(ctx.t)}
              locale={ctx.locale}
            />
          </div>
        </div>

        {p.embedUrl ? (
          <div className="container contact-map">
            <iframe
              className="map-frame"
              src={p.embedUrl}
              title={ctx.t("ui.main_office")}
              loading="lazy"
              referrerPolicy="no-referrer-when-downgrade"
              allowFullScreen
            />
          </div>
        ) : null}
      </section>
    );
  },

  /*
   * The goals variant is its own band in the design, with a wider measure and
   * its own type scale, so it takes that class rather than the generic section
   * wrapper.
   */
  rich_text: (p) => {
    if (!p.body) {
      return null;
    }

    const classes = [
      p.variant === "goals" ? "about-goals" : "section",
      p.tight ? "section--tight" : "",
      p.lined ? "lined" : "",
    ]
      .filter(Boolean)
      .join(" ");

    return (
      <section className={classes}>
        <div className="container">
          <SectionHeader kicker={p.kicker} title={p.title} />
          <RichText html={p.body} className="article-prose" />
        </div>
      </section>
    );
  },

  /*
   * aria-hidden because it is decorative motion, and the same words appear in
   * the surrounding copy. Four groups so the marquee loop has no visible seam.
   */
  ticker: (p) => {
    const phrases: string[] = p.phrases ?? [];

    if (phrases.length === 0) {
      return null;
    }

    return (
      <section className="ticker" aria-hidden="true">
        <div className="ticker__track">
          {[0, 1, 2, 3].map((group) => (
            <div className="ticker__group" key={group}>
              {phrases.map((phrase, i) => (
                <span key={i}>{phrase}</span>
              ))}
            </div>
          ))}
        </div>
      </section>
    );
  },

  accordion: (p) =>
    (p.faqs ?? []).length === 0 ? null : (
      <section className="section section--tight">
        <div className="container">
          <SectionHeader title={p.title} />
          <Accordion items={p.faqs} />
        </div>
      </section>
    ),

  /*
   * The design's about-intro: copy on one side, an accordion and a link on the
   * other. One grid, not an intro band stacked above a separate accordion.
   */
  about_intro: (p) => (
    <section className="about-intro">
      <div className="about-intro__copy">
        {p.title ? <h2 className="band-title">{p.title}</h2> : null}
        <RichText html={p.body} className={undefined} />
      </div>

      {(p.faqs ?? []).length > 0 || p.linkUrl ? (
        <div className="about-intro__accordion">
          {(p.faqs ?? []).length > 0 ? <Accordion items={p.faqs} withMark /> : null}

          {p.linkLabel && p.linkUrl ? (
            /*
             * The arrow is markup here, not the ::after glyph every other
             * .text-link uses. The stylesheet has always suppressed the glyph on
             * this link and styled a 40x8 svg in its place — but nothing ever
             * passed one, so in Arabic the link rendered with no arrow at all.
             * (English kept its glyph: `[dir="ltr"] .text-link::after` is more
             * specific than the rule switching it off, so only Arabic lost it.)
             */
            <a className="text-link about-intro__link" href={p.linkUrl}>
              {p.linkLabel}
              <svg viewBox="0 0 40 8" aria-hidden="true" focusable="false">
                <path d="M0 4h38m0 0-5-3m5 3-5 3" />
              </svg>
            </a>
          ) : null}
        </div>
      ) : null}
    </section>
  ),

  statistics: (p) =>
    (p.statistics ?? []).length === 0 ? null : (
      <section className={`figures-band${p.layout === "row" ? " figures-band--row" : ""}`}>
        {p.title ? <h2 className="figures-band__title">{p.title}</h2> : null}
        <div className="figures-grid">
          {p.statistics.map((stat: Props, i: number) => (
            <div className="figure" key={i}>
              {/* The final value is the markup, not just the data attribute. The
                  count-up still animates from zero on scroll, but the number is
                  readable with scripting off and is what a crawler sees — the
                  demo shipped a literal "0" in the HTML. */}
              <strong data-count={stat.value}>{stat.value}</strong>
              <span>{stat.label}</span>
            </div>
          ))}
        </div>
      </section>
    ),

  partners: (p, ctx) =>
    (p.partners ?? []).length === 0 ? null : (
      <section className={`partners lined${p.large ? " about-partners" : ""}`}>
        {p.title ? <h2 className="band-title">{p.title}</h2> : null}
        <Carousel
          name="partners"
          modifier="carousel--partners"
          showFoot={false}
          labels={{ previous: ctx.t("ui.previous"), next: ctx.t("ui.next") }}
        >
          {p.partners.map((partner: Props, i: number) => (
            <div className="partner" key={i}>
              {partner.url ? (
                <a href={partner.url} target="_blank" rel="noopener noreferrer">
                  <MediaImage image={partner.logo} alt={partner.name} />
                </a>
              ) : (
                <MediaImage image={partner.logo} alt={partner.name} />
              )}
            </div>
          ))}
        </Carousel>
      </section>
    ),

  /*
   * The approved design's markup verbatim: no .container, and the photo and copy
   * each keep their own wrapper. The stylesheet sizes `.leader-photo img`, so
   * dropping that wrapper — as an earlier pass did — left the portraits
   * unconstrained and stretched the section past its intended height.
   *
   * The second block mirrors, driven by index so the rhythm survives someone
   * being added or removed.
   */
  leaders: (p) => (
    <>
      {(p.leaders ?? []).map((leader: Props, i: number) => (
        <section className={`leader-section${i === 0 ? " lined" : ""}`} key={i}>
          <div className={`leader-grid${i % 2 === 0 ? "" : " leader-grid--reverse"}`}>
            <div className="leader-photo">
              <MediaImage image={leader.photo} alt={leader.name} />
            </div>
            <div className="leader-copy">
              <h2>{leader.role}</h2>
              <h3>{leader.name}</h3>
              <RichText html={leader.bio} className={undefined} />
            </div>
          </div>
        </section>
      ))}
    </>
  ),

  subsidiaries: (p) => {
    const columns: Props[] = p.columns ?? [];

    if (columns.every((column) => (column.companies ?? []).length === 0)) {
      return null;
    }

    return (
      <section className="subsidiaries lined">
        {p.title ? <h2 className="band-title">{p.title}</h2> : null}
        <div className="subsidiaries__grid">
          {columns.map((column) => (
            <div
              className={`subsidiaries__col subsidiaries__col--${column.column}`}
              key={column.column}
            >
              {(column.companies ?? []).map((company: Props, index: number) => (
                <div key={index} style={{ display: "contents" }}>
                  {/* The rule sits between the centre column's entries rather
                      than beside them, so it only exists when there is something
                      at both ends. */}
                  {column.column === "center" && index > 0 ? (
                    <span className="subsidiaries__line" aria-hidden="true" />
                  ) : null}

                  <article
                    className={`subsidiary${
                      column.column === "center" && index === 0 ? " subsidiary--lead" : ""
                    }`}
                  >
                    {company.url ? (
                      <a
                        className="subsidiary__logo"
                        href={company.url}
                        target="_blank"
                        rel="noopener noreferrer"
                      >
                        <MediaImage image={company.logo} alt={company.name} />
                      </a>
                    ) : (
                      <span className="subsidiary__logo">
                        <MediaImage image={company.logo} alt={company.name} />
                      </span>
                    )}

                    {/* A paragraph and not a heading: nothing is nested under it,
                        so the page outline stays the one heading it has. */}
                    {company.tagline ? (
                      <p className="subsidiary__tagline">{company.tagline}</p>
                    ) : null}
                  </article>
                </div>
              ))}
            </div>
          ))}
        </div>
      </section>
    );
  },

  /*
   * The number and its subject are set separately because the design gives the
   * figure a much larger face. Keeping them in one string would mean the
   * template parsing its own content back apart.
   */
  guarantees: (p) =>
    (p.guarantees ?? []).length === 0 ? null : (
      <div className="guarantees-grid">
        {p.guarantees.map((guarantee: Props, i: number) => (
          <div className="guarantee" key={i}>
            <span className="guarantee__years">{guarantee.years}</span>
            <span className="guarantee__label">{guarantee.label}</span>
          </div>
        ))}
      </div>
    ),

  services_grid: (p) =>
    (p.services ?? []).length === 0 ? null : (
      <section className="about-services" id="services">
        {p.title ? <h2 className="band-title">{p.title}</h2> : null}
        <div className="services-grid">
          {p.services.map((service: Props, i: number) => (
            <article className="service-card" key={i}>
              <ServiceIcon service={service} />
              <h3>{service.url ? <a href={service.url}>{service.title}</a> : service.title}</h3>
              {service.summary ? <p>{service.summary}</p> : null}
            </article>
          ))}
        </div>
      </section>
    ),

  /*
   * One row per service, photograph on one side and copy on the other, swapping
   * sides down the page.
   *
   * The alternation is `:nth-child(even)` in the stylesheet rather than a flag
   * on the record, because which side a row sits on is a property of its
   * position in the list — inserting a service should re-stagger the rows below
   * it, not leave two photographs side by side.
   */
  services_detail: (p) =>
    (p.services ?? []).length === 0 ? null : (
      <section className="services-detail">
        {p.services.map((service: Props, i: number) => (
          <article className="services-detail__row" key={i}>
            <div className="services-detail__media">
              {service.image ? <MediaImage image={service.image} alt="" /> : null}
            </div>
            <div className="services-detail__copy">
              <h3>{service.title}</h3>
              {service.body ? <p>{service.body}</p> : null}
            </div>
          </article>
        ))}
      </section>
    ),

  videos: (p) =>
    (p.videos ?? []).length === 0 ? null : (
      <section className="videos-section" id="videos">
        {p.title ? <h2 className="band-title">{p.title}</h2> : null}
        <div className="videos-grid">
          {p.videos.map((video: Props, i: number) => (
            <VideoEmbed key={i} video={video} />
          ))}
        </div>
      </section>
    ),

  articles_grid: (p, ctx) =>
    (p.posts ?? []).length === 0 ? null : (
      <section className="section articles-section">
        <div className="container">
          <SectionHeader title={p.title} />
          <div className="articles-grid">
            {p.posts.map((post: Props) => (
              <ArticleCard key={post.id} post={post} t={ctx.t} />
            ))}
          </div>
          {p.ctaUrl ? (
            <div className="articles-section__more">
              <Btn href={p.ctaUrl}>{p.ctaLabel ?? ctx.t("ui.view_all")}</Btn>
            </div>
          ) : null}
        </div>
      </section>
    ),

  projects_carousel: (p, ctx) => {
    const projects: Props[] = p.projects ?? [];

    if (projects.length === 0) {
      return null;
    }

    const labels = { previous: ctx.t("ui.previous"), next: ctx.t("ui.next") };

    // Six projects in two rows of three, which is how the published services
    // page closes. Nothing to step through, so nothing to drive.
    if (p.variant === "grid") {
      return (
        <section className="projects-section projects-section--grid">
          <div className="projects-shell">
            {p.title ? <h2 className="band-title">{p.title}</h2> : null}
            <div className="projects-grid">
              {projects.map((project, i) => (
                <article className="project-slide" key={project.id ?? i}>
                  <a href={project.url ?? undefined}>
                    <MediaImage image={project.image} alt={project.title} />
                    <h3>{project.title}</h3>
                  </a>
                </article>
              ))}
            </div>
          </div>
        </section>
      );
    }

    if (p.variant === "about") {
      return (
        <section className="about-projects">
          {p.title ? <h2 className="band-title">{p.title}</h2> : null}
          <div className="about-projects__grid">
            {p.featureImage ? (
              <div className="about-projects__feature">
                <MediaImage image={p.featureImage} alt={p.featureImage.alt || p.title || ""} />
              </div>
            ) : null}

            <Carousel name="about-projects" modifier="carousel--wide" labels={labels}>
              {projects.map((project, i) => (
                <article className="project-slide project-slide--wide" key={project.id ?? i}>
                  <a className="project-slide__media" href={project.url ?? undefined}>
                    <MediaImage image={project.image} alt={project.title} />
                  </a>
                  <div className="project-slide__content">
                    <h3>
                      <a href={project.url ?? undefined}>{project.title}</a>
                    </h3>
                    {project.projectType ? (
                      <p className="project-slide__cats">{project.projectType.name}</p>
                    ) : null}
                  </div>
                </article>
              ))}
            </Carousel>
          </div>
        </section>
      );
    }

    return (
      <section className="projects-section">
        <div className="projects-shell">
          {p.title ? <h2 className="band-title">{p.title}</h2> : null}

          <Carousel name="projects" labels={labels}>
            {projects.map((project, i) => (
              <article className="project-slide" key={project.id ?? i}>
                <a href={project.url ?? undefined}>
                  <MediaImage image={project.image} alt={project.title} eager={i === 0} />
                  <h3>{project.title}</h3>
                </a>
              </article>
            ))}
          </Carousel>

          {p.allProjectsUrl ? (
            <a className="btn projects-section__link" href={p.allProjectsUrl}>
              {ctx.t("ui.all_projects")}
            </a>
          ) : null}
        </div>
      </section>
    );
  },

  post_gallery: (p, ctx) =>
    (p.slides ?? []).length === 0 ? null : (
      <Gallery items={p.slides} name="post-gallery" labels={{ previous: ctx.t("ui.previous"), next: ctx.t("ui.next") }} />
    ),

  map: (p, ctx) =>
    p.embedUrl ? (
      <section className="map-section">
        <iframe
          className="map-frame"
          src={p.embedUrl}
          title={ctx.t("projects.location_section")}
          loading="lazy"
          referrerPolicy="no-referrer-when-downgrade"
          allowFullScreen
        />
      </section>
    ) : null,
};

function ServiceIcon({ service }: { service: Props }) {
  if (service.iconSvg) {
    return (
      <span
        className="service-card__icon"
        aria-hidden="true"
        // Admin-authored markup restricted to the highest permission level.
        // Trusted here exactly as the Blade template trusts it.
        dangerouslySetInnerHTML={{ __html: service.iconSvg }}
      />
    );
  }

  if (service.icon) {
    return (
      <span className="service-card__icon">
        <MediaImage image={service.icon} alt="" />
      </span>
    );
  }

  return null;
}

/**
 * An unknown block renders nothing rather than throwing.
 *
 * A section type added in Filament before the frontend ships must not take the
 * page down with it. It is logged so "nothing rendered" does not become a silent
 * gap nobody notices.
 */
export function Sections({ sections, ctx }: { sections: Section[]; ctx: RenderContext }) {
  return (
    <>
      {sections.map((section) => {
        const Component = SECTIONS[section.type];

        if (!Component) {
          console.warn(`[sections] no component for block type "${section.type}"`);
          return null;
        }

        return <div key={section.id} style={{ display: "contents" }}>{Component(section.props, ctx)}</div>;
      })}
    </>
  );
}

export const SECTION_TYPES = Object.keys(SECTIONS);
