import type { CSSProperties, ReactNode } from "react";
import { activeIntegrations } from "@/lib/integrations/runtime";
import Link from "next/link";
import Script from "next/script";
import { RailMenu, RailSocial } from "./rail-menu";
import { getMenu, type NavNode, type PublicSite } from "@/lib/public/site";
import { mediaUrl } from "@/lib/media/urls";
import { messagesForSite } from "@/lib/services/org-translations";
import { ScrollTop } from "./scroll-top";
import { loadTheme } from "@/lib/themes/registry";
import { PALETTE_KEYS } from "@/lib/site-settings";


const TITLE_SIZES = { md: "1rem", lg: "1.25rem", xl: "1.5rem", "2xl": "1.875rem", "3xl": "2.25rem" } as const;
const BTN_SIZES = { "x-small": "0.75rem", small: "0.875rem", normal: "1rem", large: "1.125rem", "x-large": "1.25rem" } as const;

/**
 * Every value of Site Settings > Design as a CSS variable on the site root, the way ShowOff exposes them. Themes and custom CSS
 * can use them (var(--btn-radius), var(--color-one), var(--font-two) ...) and the rules below read the same variables.
 */
export function designVars(design: PublicSite["theme"]["design"]): Record<string, string> {
  const t = design.pageTitle;
  const b = design.elements.button;
  const im = design.elements.image;
  const sb = design.scrollbar;
  const st = design.scrollTop;
  const fontOf = (w: "one" | "two") => (w === "one" ? design.fontOne : design.fontTwo);
  const tf = fontOf(t.font);
  const bf = fontOf(b.font);
  const weightOne = design.fontOne.source === "system" ? "400" : String(design.fontOne.regular);
  const v: Record<string, string> = {
    "--font-one": fontStack(design.fontOne),
    "--font-two": fontStack(design.fontTwo),
    "--font-one-regular": String(design.fontOne.regular), "--font-one-bold": String(design.fontOne.bold),
    "--font-two-regular": String(design.fontTwo.regular), "--font-two-bold": String(design.fontTwo.bold),
    "--font-weight": weightOne,
    "--direction": design.direction, "--page-title-font": `var(--font-${t.font})`, "--page-title-padding-top": `${t.paddingTop}px`, "--page-title-align": t.align, "--page-title-size": TITLE_SIZES[t.size],
    "--page-title-weight": String(t.bold ? tf.bold : tf.regular), "--page-title-color": t.color, "--page-title-transform": t.uppercase ? "uppercase" : "none",
    "--btn-font": `var(--font-${b.font})`, "--btn-size": BTN_SIZES[b.size], "--btn-weight": String(b.bold ? bf.bold : bf.regular),
    "--btn-radius": `${b.radius}px`, "--btn-transform": b.uppercase ? "uppercase" : "none", "--btn-border-width": `${b.borderWidth}px`,
    "--img-radius": `${im.radius}px`, "--img-border-width": `${im.borderWidth}px`, "--img-border-colour": im.borderColour || "currentColor",
    "--scrollbar-width": `${sb.width}px`, "--scrollbar-track": sb.track, "--scrollbar-thumb": sb.thumb,
    "--scroll-top-background": st.background, "--scroll-top-icon": st.icon, "--scroll-top-radius": `${st.radius}px`, "--scroll-top-position": st.position,
  };
  if (b.textColour) v["--btn-text"] = b.textColour;
  if (b.background) v["--btn-bg"] = b.background;
  if (b.borderColour) v["--btn-border-colour"] = b.borderColour;
  if (b.hoverBackground) v["--btn-hover-bg"] = b.hoverBackground;
  if (b.hoverText) v["--btn-hover-text"] = b.hoverText;
  if (im.ratio !== "auto") v["--img-ratio"] = im.ratio.replace(":", "/");
  return v;
}

export function themeStyle(theme: PublicSite["theme"]): CSSProperties {
  return {
    "--primary": theme.primary,
    "--background": theme.background,
    "--foreground": theme.text,
    "--card": theme.background,
    "--border": `color-mix(in srgb, ${theme.text} 15%, ${theme.background})`,
    "--muted": `color-mix(in srgb, ${theme.text} 6%, ${theme.background})`,
    "--primary-foreground": "#ffffff",
    "--site-heading-font": fontStack(theme.design.fontTwo.source === "system" && theme.design.fontTwo.generic === "sans" ? { ...theme.design.fontTwo, generic: theme.headingFont } : theme.design.fontTwo),
    "--site-body-font": fontStack(theme.design.fontOne),
    "--muted-text": theme.design.textMuted,
    "--link": theme.design.link,
    ...Object.fromEntries(Object.entries(theme.design.palette).map(([k, v]) => [`--color-${k}`, v])),
    ...designVars(theme.design),
    background: "var(--background)",
    color: "var(--foreground)",
    fontFamily: "var(--site-body-font)",
  } as CSSProperties;
}

/** Design settings that need real CSS rules. Every interpolated value is a validated hex colour, integer or fixed keyword. */
import { fontHref, fontStack } from "@/lib/public/fonts";

export function designCss(design: PublicSite["theme"]["design"]): string {
  const bold = design.fontOne.source === "system" ? "bold" : String(design.fontOne.bold);
  const rules = [
    `.site-heading{font-family:var(--site-heading-font)}`,
    `.site-page-title{font-family:var(--page-title-font);padding-top:var(--page-title-padding-top);text-align:var(--page-title-align);font-size:var(--page-title-size);font-weight:var(--page-title-weight);color:var(--page-title-color);text-transform:var(--page-title-transform)}`,
    `main p a,main li a{color:var(--link)}`,
    `strong,b{font-weight:${bold}}`,
  ];
  if (design.fontOne.source !== "system") rules.push(`body{font-weight:${design.fontOne.regular}}`);
  const b = design.elements.button;
  rules.push(
    `.site-btn{font-family:var(--btn-font);font-size:var(--btn-size);font-weight:var(--btn-weight);border-radius:var(--btn-radius);text-transform:var(--btn-transform);${b.textColour ? "color:var(--btn-text);" : ""}${b.background ? "background:var(--btn-bg);" : ""}${b.borderWidth ? "border:var(--btn-border-width) solid var(--btn-border-colour,currentColor);" : ""}}`,
    `.site-btn:hover{${b.hoverBackground ? "background:var(--btn-hover-bg);" : ""}${b.hoverText ? "color:var(--btn-hover-text);" : ""}}`,
  );
  // Common look of every page-builder block. Everything reads Site Settings > Design variables, so changing a colour, font or button
  // there changes all blocks at once.
  rules.push(
    ".blk{color:var(--foreground)}",
    "[data-anim]{transition:opacity var(--anim-d,700ms) ease var(--anim-delay,0ms),transform var(--anim-d,700ms) ease var(--anim-delay,0ms)}",
    "[data-anim=armed]{opacity:0}",
    "[data-anim=armed][data-anim-type=slideUp]{transform:translateY(40px)}",
    "[data-anim=armed][data-anim-type=slideLeft]{transform:translateX(60px)}",
    "[data-anim=armed][data-anim-type=slideRight]{transform:translateX(-60px)}",
    "[data-anim=armed][data-anim-type=zoom]{transform:scale(.92)}",
    "[data-anim=in]{opacity:1;transform:none}",
    "@media (prefers-reduced-motion:reduce){[data-anim]{transition:none}}",
    ".site-on-dark .site-heading,.site-on-dark .blk{color:inherit}",
    ".site-on-dark .blk-card,.site-on-dark .blk-card .blk{color:var(--foreground)}",
    ".blk-bg-muted{background:var(--muted)}",
    ".blk-bg-primary{background:var(--primary);color:var(--primary-foreground)}",
    ".blk-bg-dark{background:var(--foreground);color:var(--background)}",
    ".blk-py-sm{padding-block:1.5rem}.blk-py-md{padding-block:3rem}.blk-py-lg{padding-block:5rem}",
    ".blk-card{border:1px solid var(--border);background:var(--card);color:var(--foreground);border-radius:var(--blk-radius,.75rem)}",
    ".blk-btn{display:inline-flex;align-items:center;justify-content:center;padding:.625rem 1.25rem;border:var(--btn-border-width) solid var(--btn-border-colour,transparent);background:var(--btn-bg,var(--primary));color:var(--btn-text,var(--primary-foreground));font-family:var(--btn-font);font-size:var(--btn-size);font-weight:var(--btn-weight);border-radius:var(--btn-radius);text-transform:var(--btn-transform);text-decoration:none;cursor:pointer;transition:background-color .15s,color .15s,opacity .15s}",
    ".blk-btn:hover{background:var(--btn-hover-bg,var(--btn-bg,var(--primary)));color:var(--btn-hover-text,var(--btn-text,var(--primary-foreground)));opacity:.9}",
    ".blk-btn-inverse{background:var(--background);color:var(--foreground);border-color:transparent}.blk-btn-inverse:hover{background:var(--background);color:var(--foreground)}",
    ".blk-title{color:inherit}",
  );
  const im = design.elements.image;
  rules.push(`.site-img{border-radius:var(--img-radius);${im.borderWidth ? "border:var(--img-border-width) solid var(--img-border-colour);" : ""}${im.ratio !== "auto" ? "aspect-ratio:var(--img-ratio);object-fit:cover;width:100%;" : ""}}`);
  if (design.scrollbar.enabled) {
    rules.push("html{scrollbar-color:var(--scrollbar-thumb) var(--scrollbar-track)}", "::-webkit-scrollbar{width:var(--scrollbar-width);height:var(--scrollbar-width)}", "::-webkit-scrollbar-track{background:var(--scrollbar-track)}", "::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb)}");
  }
  for (const k of PALETTE_KEYS) rules.push(`.site-color-${k}{color:var(--color-${k})}`, `.site-bg-${k}{background-color:var(--color-${k})}`);
  rules.push(".site-text-muted{color:var(--muted-text)}");
  return rules.join("");
}

/** Stylesheets for web fonts the design asks for (Google Fonts, Adobe, custom). */
export function FontLinks({ design }: { design: PublicSite["theme"]["design"] }) {
  const hrefs = [...new Set([fontHref(design.fontOne), fontHref(design.fontTwo)].filter((h): h is string => Boolean(h)))];
  return <>{hrefs.map((h) => <link key={h} rel="stylesheet" href={h} precedence="default" />)}</>;
}

function NavLinks({ items, className }: { items: NavNode[]; className?: string }) {
  return (
    <ul className={className}>
      {items.map((n) => (
        <li key={n.id} className="group relative">
          <NavLink node={n} />
          {n.children.length > 0 && (
            <ul className="mt-1 space-y-1 pl-3 text-sm sm:absolute sm:left-0 sm:z-10 sm:hidden sm:min-w-44 sm:rounded-md sm:border sm:border-border sm:bg-[var(--background)] sm:p-2 sm:group-hover:block">
              {n.children.map((c) => (
                <li key={c.id}>
                  <NavLink node={c} />
                </li>
              ))}
            </ul>
          )}
        </li>
      ))}
    </ul>
  );
}

function NavLink({ node }: { node: NavNode }) {
  const cls = `inline-block py-1 hover:underline${node.style ? ` ${node.style}` : ""}`;
  if (node.external) {
    return (
      <a href={node.href} className={cls} rel="noopener noreferrer" {...(node.newTab ? { target: "_blank" } : {})}>
        {node.label}
      </a>
    );
  }
  return <Link href={node.href} className={cls} {...(node.newTab ? { target: "_blank", rel: "noopener noreferrer" } : {})}>{node.label}</Link>;
}

/** Social profile links from Site Settings, topped up by social integrations. */
async function socialLinksFor(site: PublicSite): Promise<[string, string][]> {
  const links: Record<string, string> = { ...site.settings.social };
  for (const i of await activeIntegrations(site.id, "social")) for (const [k, v] of Object.entries(((i.config as { links?: Record<string, string> }).links) ?? {})) if (v && !links[k]) links[k] = v;
  return Object.entries(links).filter(([, v]) => v);
}

/** True when the website's theme puts the Header, Hero and Footer panels in the theme frame (theme.json "panelFrame"). */
export async function usesPanelFrame(site: Pick<PublicSite, "theme">): Promise<boolean> {
  if (!site.theme.themeKey) return false;
  return (await loadTheme(site.theme.themeKey))?.manifest.panelFrame === true;
}

/** The footer menu, social links and copyright line. */
function FooterBar({ footer, social, note, label }: { footer: NavNode[]; social: [string, string][]; note: string; label?: string }) {
  return (
    <div className="site-footer-bar mx-auto w-full max-w-6xl space-y-4 px-4 py-8 text-sm sm:px-6">
      {footer.length > 0 && (
        <nav aria-label={label}>
          <NavLinks items={footer} className="flex flex-wrap gap-x-6 gap-y-1" />
        </nav>
      )}
      {social.length > 0 && (
        <ul className="flex flex-wrap gap-4">
          {social.map(([k, v]) => (
            <li key={k}>
              <a href={v} rel="noopener noreferrer me" target="_blank" className="capitalize hover:underline">{k}</a>
            </li>
          ))}
        </ul>
      )}
      <p className="opacity-75">{note}</p>
    </div>
  );
}

/**
 * Page zones for a theme with "panelFrame": header panels in a header strip, hero panels under it, the page in <main>, and footer panels
 * inside the site <footer> above the menu / social / copyright bar. SiteFrame leaves <main> and <footer> to this component in that mode.
 */
export async function FramedPage({ site, header, hero, footer, children }: { site: PublicSite; header?: ReactNode[]; hero?: ReactNode[]; footer?: ReactNode[]; children: ReactNode }) {
  const [menu, social, text] = await Promise.all([getMenu(site.id, "footer"), socialLinksFor(site), messagesForSite(site.id)]);
  const title = site.settings.siteTitle || site.name;
  return (
    <>
      {header?.length ? <header className="site-top" data-zone="header">{header}</header> : null}
      {hero?.length ? <div className="site-hero" data-zone="hero">{hero}</div> : null}
      <main id="main" className="flex-1">{children}</main>
      <footer className="site-footer border-t border-border bg-[var(--muted)]">
        {footer?.length ? <div className="site-footer-panels" data-zone="footer">{footer}</div> : null}
        <FooterBar footer={menu} social={social} label={text["layout.footerNav"]} note={site.settings.footerText || `© ${new Date().getFullYear()} ${title}`} />
      </footer>
    </>
  );
}

/** For pages that are not page-builder pages (forms, 404): wraps them in the theme zones when the theme uses panelFrame. */
export async function ThemedPage({ site, children }: { site: PublicSite | null; children: ReactNode }) {
  if (!site || !(await usesPanelFrame(site))) return <>{children}</>;
  return <FramedPage site={site}>{children}</FramedPage>;
}

export async function SiteFrame({ site, header, footer, children, banner }: { site: PublicSite; header: NavNode[]; footer: NavNode[]; children: ReactNode; banner?: ReactNode }) {
  const { settings } = site;
  const text = await messagesForSite(site.id);
  const title = settings.siteTitle || site.name;
  const [integrationAnalytics, social] = await Promise.all([activeIntegrations(site.id, "analytics"), socialLinksFor(site)]);
  // A theme assigned by a super admin (src/themes/<key>) supplies default design values (already merged into site.theme) and extra CSS.
  const custom = site.theme.themeKey ? await loadTheme(site.theme.themeKey) : null;
  // With "panelFrame" the page renders <main> and <footer> itself (FramedPage) so the Header/Hero/Footer panels sit in the frame.
  const framed = custom?.manifest.panelFrame === true;
  return (
    <div style={themeStyle(site.theme)} dir={site.theme.design.direction} className={`flex min-h-screen flex-col${custom ? ` theme-${custom.key}` : ""}`}>
      {custom?.css ? <style data-theme={custom.key} dangerouslySetInnerHTML={{ __html: custom.css }} /> : null}
      <FontLinks design={site.theme.design} />
      <style>{designCss(site.theme.design)}</style>
      {banner}
      {custom?.manifest.navigation === "rail" ? (
        <header className="site-rail">
          <div className="rail-inner">
            <div className="rail-top">
              <Link href="/" className="rail-btn rail-home" aria-label={title}>
                <svg aria-hidden viewBox="0 0 24 24" width="20" height="18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 11l9-8 9 8" /><path d="M5 10v10h5v-6h4v6h5V10" /></svg>
              </Link>
            </div>
            <button type="button" className="rail-toggle" aria-label={text["layout.mainNav"] ?? "Main menu"} aria-controls="rail-nav">
              <span /><span /><span />
            </button>
            {header.length > 0 && <RailMenu items={header} label={text["layout.mainNav"] ?? "Main menu"} />}
            <RailSocial links={social} />
          </div>
        </header>
      ) : (
      <header className="border-b border-border">
        <div className="mx-auto flex w-full max-w-6xl flex-wrap items-center justify-between gap-4 px-4 py-4 sm:px-6">
          <Link href="/" className="flex items-center gap-3 text-lg font-bold">
            {settings.logoMediaId && (
              // eslint-disable-next-line @next/next/no-img-element -- our own media route
              <img src={mediaUrl(settings.logoMediaId)} alt="" className="h-8 w-auto" />
            )}
            <span>{title}</span>
          </Link>
          {header.length > 0 && (
            <nav aria-label={text["layout.mainNav"] ?? "Main menu"}>
              <NavLinks items={header} className="flex flex-wrap gap-x-6 gap-y-1 text-sm font-medium" />
            </nav>
          )}
        </div>
      </header>
      )}
      {framed ? children : (
        <>
          <main id="main" className="flex-1">{children}</main>
          <footer className="site-footer border-t border-border bg-[var(--muted)]">
            <FooterBar footer={footer} social={social} label={text["layout.footerNav"]} note={settings.footerText || `© ${new Date().getFullYear()} ${title}`} />
          </footer>
        </>
      )}
      {site.theme.design.scrollTop.enabled ? <ScrollTop {...site.theme.design.scrollTop} label={text["layout.backToTop"] ?? "Back to top"} /> : null}
      <Analytics settings={settings} />
      <IntegrationAnalytics items={integrationAnalytics.map((i) => i.config as { provider: string; trackingId: string }).filter((c) => !(c.provider === settings.analytics.provider && c.trackingId === settings.analytics.id))} />
      <ThirdParty items={settings.thirdParty} />
    </div>
  );
}

function Analytics({ settings }: { settings: PublicSite["settings"] }) {
  const { provider, id } = settings.analytics;
  if (provider === "ga4") {
    return (
      <>
        <Script src={`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(id)}`} strategy="afterInteractive" />
        <Script id="ga4-init" strategy="afterInteractive">{`window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());gtag('config',${JSON.stringify(id)},{anonymize_ip:true});`}</Script>
      </>
    );
  }
  if (provider === "plausible") return <Script defer data-domain={id} src="https://plausible.io/js/script.js" strategy="afterInteractive" />;
  return null;
}

/** Analytics added through Integrations (ids are validated when saved, and escaped again here). */
function IntegrationAnalytics({ items }: { items: { provider: string; trackingId: string }[] }) {
  return (
    <>
      {items.map((c) => {
        const id = c.trackingId;
        const key = `int-${c.provider}-${id}`;
        if (c.provider === "ga4") return (
          <span key={key}>
            <Script src={`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(id)}`} strategy="afterInteractive" />
            <Script id={`${key}-init`} strategy="afterInteractive">{`window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());gtag('config',${JSON.stringify(id)},{anonymize_ip:true});`}</Script>
          </span>
        );
        if (c.provider === "gtm") return <Script key={key} id={key} strategy="afterInteractive">{`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s);j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer',${JSON.stringify(id)});`}</Script>;
        if (c.provider === "plausible") return <Script key={key} defer data-domain={id} src="https://plausible.io/js/script.js" strategy="afterInteractive" />;
        if (c.provider === "meta-pixel") return <Script key={key} id={key} strategy="afterInteractive">{`!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');fbq('init',${JSON.stringify(id)});fbq('track','PageView');`}</Script>;
        return null;
      })}
    </>
  );
}

/** Custom code added by a platform administrator. CSS becomes a stylesheet, JavaScript runs after the page loads. */
function ThirdParty({ items }: { items: PublicSite["settings"]["thirdParty"] }) {
  return (
    <>
      {items.filter((c) => c.active).map((c) =>
        c.kind === "css"
          ? <style key={c.id} data-third-party={c.id}>{c.code}</style>
          : <Script key={c.id} id={`tp-${c.id}`} strategy="afterInteractive">{c.code}</Script>)}
    </>
  );
}
