"use client";

import { createContext, CSSProperties, useContext, useEffect } from "react";

// Context for cardBorderHidden
export const CardBorderHiddenContext = createContext<boolean>(false);

export function useCardBorderHiddenContext() {
  return useContext(CardBorderHiddenContext);
}

// Context for hasBackgroundImage
export const HasBackgroundImageContext = createContext<boolean>(false);

export function useHasBackgroundImageContext() {
  return useContext(HasBackgroundImageContext);
}
import {
  colorToString,
  useColorAttribute,
  useColorAttributeNullable,
} from "./useColorAttribute";
import { Color as AriaColor, parseColor } from "react-aria-components";

import { useEntity } from "src/replicache";
import { useLeafletPublicationData } from "components/PageSWRDataProvider";
import {
  PublicationBackgroundProvider,
  PublicationThemeProvider,
} from "./PublicationThemeProvider";
import { compareColors } from "./themeUtils";
import {
  getFontConfig,
  getGoogleFontsUrl,
  getFontFamilyValue,
  getFontBaseSize,
  defaultFontId,
} from "src/fonts";

// define a function to set an Aria Color to a CSS Variable in RGB
function setCSSVariableToColor(
  el: HTMLElement,
  name: string,
  value: AriaColor,
) {
  el?.style.setProperty(name, colorToString(value, "rgb"));
}

//Create a wrapper that applies a theme to each page
export function ThemeProvider(props: {
  entityID: string | null;
  local?: boolean;
  children: React.ReactNode;
  className?: string;
  initialHeadingFontId?: string;
  initialBodyFontId?: string;
}) {
  let { data: pub, normalizedPublication } = useLeafletPublicationData();
  if (!pub || !pub.publications) return <LeafletThemeProvider {...props} />;
  return (
    <PublicationThemeProvider
      {...props}
      theme={normalizedPublication?.theme}
      pub_creator={pub.publications?.identity_did}
    />
  );
}
// for PUBLICATIONS: define Aria Colors for each value and use BaseThemeProvider to wrap the content of the page in the theme

// for LEAFLETS : define Aria Colors for each value and use BaseThemeProvider to wrap the content of the page in the theme
export function LeafletThemeProvider(props: {
  entityID: string | null;
  local?: boolean;
  children: React.ReactNode;
  initialHeadingFontId?: string;
  initialBodyFontId?: string;
}) {
  let bgLeaflet = useColorAttribute(props.entityID, "theme/page-background");
  let bgPage = useColorAttribute(props.entityID, "theme/card-background");
  let cardBorderHiddenValue = useEntity(
    props.entityID,
    "theme/card-border-hidden",
  )?.data.value;
  let showPageBackground = !cardBorderHiddenValue;
  let backgroundImage = useEntity(props.entityID, "theme/background-image");
  let hasBackgroundImage = !!backgroundImage;
  let primary = useColorAttribute(props.entityID, "theme/primary");

  let highlight1 = useEntity(props.entityID, "theme/highlight-1");
  let highlight2 = useColorAttribute(props.entityID, "theme/highlight-2");
  let highlight3 = useColorAttribute(props.entityID, "theme/highlight-3");

  let accent1 = useColorAttribute(props.entityID, "theme/accent-background");
  let accent2 = useColorAttribute(props.entityID, "theme/accent-text");

  let pageWidth = useEntity(props.entityID, "theme/page-width");
  // Use initial font IDs as fallback until Replicache syncs
  let headingFontId =
    useEntity(props.entityID, "theme/heading-font")?.data.value ??
    props.initialHeadingFontId;
  let bodyFontId =
    useEntity(props.entityID, "theme/body-font")?.data.value ??
    props.initialBodyFontId;

  return (
    <CardBorderHiddenContext.Provider value={!!cardBorderHiddenValue}>
      <HasBackgroundImageContext.Provider value={hasBackgroundImage}>
        <BaseThemeProvider
          local={props.local}
          bgLeaflet={bgLeaflet}
          bgPage={bgPage}
          primary={primary}
          highlight2={highlight2}
          highlight3={highlight3}
          highlight1={highlight1?.data.value}
          accent1={accent1}
          accent2={accent2}
          showPageBackground={showPageBackground}
          pageWidth={pageWidth?.data.value}
          hasBackgroundImage={hasBackgroundImage}
          headingFontId={headingFontId}
          bodyFontId={bodyFontId}
        >
          {props.children}
        </BaseThemeProvider>
      </HasBackgroundImageContext.Provider>
    </CardBorderHiddenContext.Provider>
  );
}

// handles setting all the Aria Color values to CSS Variables and wrapping the page the theme providers
export const BaseThemeProvider = ({
  local,
  bgLeaflet,
  bgPage: bgPageProp,
  primary,
  accent1,
  accent2,
  highlight1,
  highlight2,
  highlight3,
  showPageBackground,
  pageWidth,
  hasBackgroundImage,
  headingFontId,
  bodyFontId,
  className,
  children,
}: {
  local?: boolean;
  showPageBackground?: boolean;
  hasBackgroundImage?: boolean;
  bgLeaflet: AriaColor;
  bgPage: AriaColor;
  primary: AriaColor;
  accent1: AriaColor;
  accent2: AriaColor;
  highlight1?: string;
  highlight2: AriaColor;
  highlight3: AriaColor;
  pageWidth?: number;
  headingFontId?: string;
  bodyFontId?: string;
  className?: string;
  children: React.ReactNode;
}) => {
  // When showPageBackground is false and there's no background image,
  // pageBg should inherit from leafletBg
  const bgPage =
    !showPageBackground && !hasBackgroundImage ? bgLeaflet : bgPageProp;

  let accentContrast;
  let bgRef = colorToString(showPageBackground ? bgPage : bgLeaflet, "rgb");
  let primaryStr = colorToString(primary, "rgb");

  let sortedAccents = [accent1, accent2].sort((a, b) => {
    // sort accents by contrast against the background
    return (
      compareColors(colorToString(b, "rgb"), bgRef).distance -
      compareColors(colorToString(a, "rgb"), bgRef).distance
    );
  });

  let bestVsText = compareColors(
    colorToString(sortedAccents[0], "rgb"),
    primaryStr,
  );
  let altVsBg = compareColors(colorToString(sortedAccents[1], "rgb"), bgRef);

  if (
    // if the contrast-y accent is too similar to text color
    // (close in distance AND not distinguishable by hue/chroma)
    bestVsText.distance < 0.15 &&
    bestVsText.chromaDiff < 0.05 &&
    // and if the other accent is different enough from the background
    altVsBg.distance > 0.31
  ) {
    //then choose the less contrast-y accent
    accentContrast = sortedAccents[1];
  } else {
    // otherwise, choose the more contrast-y option
    accentContrast = sortedAccents[0];
  }

  // Check if the accent contrast color is visually similar to the text color.
  // We check both overall OKLab distance AND chroma (hue/saturation) difference
  // because dark colors are compressed in OKLab lightness — a dark blue accent
  // vs black text can have a small OKLab distance yet be clearly distinguishable
  // by hue. If the chroma difference is significant, the colors are visually
  // distinct and don't need an underline to tell them apart.
  let accentVsText = compareColors(
    colorToString(accentContrast, "rgb"),
    primaryStr,
  );
  let accentContrastSimilarToText =
    accentVsText.distance < 0.45 && accentVsText.chromaDiff < 0.05;

  // Get font configs for CSS variables.
  // When using the default font (Quattro), use var(--font-quattro) which is
  // always available via next/font/local in layout.tsx, rather than the raw
  // font-family name 'iA Writer Quattro V' which depends on a dynamic @font-face.
  // When both heading and body are default, omit the variables entirely so the
  // CSS fallback var(--theme-font, var(--font-quattro)) resolves naturally.
  const isDefaultBody = !bodyFontId || bodyFontId === defaultFontId;
  const isDefaultHeading = !headingFontId || headingFontId === defaultFontId;
  const headingFontConfig = getFontConfig(headingFontId);
  const bodyFontConfig = getFontConfig(bodyFontId);
  const headingFontValue = isDefaultHeading
    ? isDefaultBody
      ? undefined
      : "var(--font-quattro)"
    : getFontFamilyValue(headingFontConfig);
  const bodyFontValue = isDefaultBody
    ? isDefaultHeading
      ? undefined
      : "var(--font-quattro)"
    : getFontFamilyValue(bodyFontConfig);
  const bodyFontBaseSize = isDefaultBody
    ? undefined
    : getFontBaseSize(bodyFontConfig);
  const headingGoogleFontsUrl = getGoogleFontsUrl(headingFontConfig);
  const bodyGoogleFontsUrl = getGoogleFontsUrl(bodyFontConfig);

  // Dynamically load Google Fonts when fonts change
  useEffect(() => {
    const loadGoogleFont = (url: string | null, fontFamily: string) => {
      if (!url) return;

      // Check if this font stylesheet is already in the document
      const existingLink = document.querySelector(`link[href="${url}"]`);
      if (existingLink) return;

      // Add preconnect hints if not present
      if (
        !document.querySelector('link[href="https://fonts.googleapis.com"]')
      ) {
        const preconnect1 = document.createElement("link");
        preconnect1.rel = "preconnect";
        preconnect1.href = "https://fonts.googleapis.com";
        document.head.appendChild(preconnect1);

        const preconnect2 = document.createElement("link");
        preconnect2.rel = "preconnect";
        preconnect2.href = "https://fonts.gstatic.com";
        preconnect2.crossOrigin = "anonymous";
        document.head.appendChild(preconnect2);
      }

      // Load the Google Font stylesheet
      const link = document.createElement("link");
      link.rel = "stylesheet";
      link.href = url;
      document.head.appendChild(link);

      // Wait for the font to actually load before it gets applied
      if (document.fonts?.load) {
        document.fonts.load(`1em "${fontFamily}"`);
      }
    };

    loadGoogleFont(headingGoogleFontsUrl, headingFontConfig.fontFamily);
    loadGoogleFont(bodyGoogleFontsUrl, bodyFontConfig.fontFamily);
  }, [
    headingGoogleFontsUrl,
    bodyGoogleFontsUrl,
    headingFontConfig.fontFamily,
    bodyFontConfig.fontFamily,
  ]);

  useEffect(() => {
    if (local) return;
    let el = document.querySelector(":root") as HTMLElement;
    if (!el) return;
    setCSSVariableToColor(el, "--bg-leaflet", bgLeaflet);
    setCSSVariableToColor(el, "--bg-page", bgPage);
    document.body.style.backgroundColor = `rgb(${colorToString(bgLeaflet, "rgb")})`;
    document
      .querySelector('meta[name="theme-color"]')
      ?.setAttribute("content", `rgb(${colorToString(bgLeaflet, "rgb")})`);
    el?.style.setProperty(
      "--bg-page-alpha",
      bgPage.getChannelValue("alpha").toString(),
    );
    setCSSVariableToColor(el, "--primary", primary);

    setCSSVariableToColor(el, "--highlight-2", highlight2);
    setCSSVariableToColor(el, "--highlight-3", highlight3);

    //highlight 1 is special because its default value is a calculated value
    if (highlight1) {
      let color = parseColor(`hsba(${highlight1})`);
      el?.style.setProperty(
        "--highlight-1",
        `rgb(${colorToString(color, "rgb")})`,
      );
    } else {
      el?.style.setProperty(
        "--highlight-1",
        "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 75%)",
      );
    }
    setCSSVariableToColor(el, "--accent-1", accent1);
    setCSSVariableToColor(el, "--accent-2", accent2);
    el?.style.setProperty(
      "--accent-contrast",
      colorToString(accentContrast, "rgb"),
    );
    el?.style.setProperty(
      "--accent-1-is-contrast",
      accentContrast === accent1 ? "1" : "0",
    );
    el?.style.setProperty(
      "--accent-contrast-similar-to-text",
      accentContrastSimilarToText ? "1" : "0",
    );
    el?.style.setProperty(
      "--link-underline",
      accentContrastSimilarToText ? "underline" : "none",
    );

    // Set page width CSS variable
    el?.style.setProperty(
      "--page-width-setting",
      (pageWidth || 624).toString(),
    );
  }, [
    local,
    bgLeaflet,
    bgPage,
    primary,
    highlight1,
    highlight2,
    highlight3,
    accent1,
    accent2,
    accentContrast,
    accentContrastSimilarToText,
    pageWidth,
  ]);
  return (
    <div
      className={`leafletWrapper w-full text-primary h-full min-h-fit flex flex-col bg-center items-stretch ${className || ""}`}
      style={
        {
          "--bg-leaflet": colorToString(bgLeaflet, "rgb"),
          "--bg-page": colorToString(bgPage, "rgb"),
          "--bg-page-alpha": bgPage.getChannelValue("alpha"),
          "--primary": colorToString(primary, "rgb"),
          "--accent-1": colorToString(accent1, "rgb"),
          "--accent-2": colorToString(accent2, "rgb"),
          "--accent-contrast": colorToString(accentContrast, "rgb"),
          "--accent-1-is-contrast": accentContrast === accent1 ? 1 : 0,
          "--accent-contrast-similar-to-text": accentContrastSimilarToText
            ? 1
            : 0,
          "--link-underline": accentContrastSimilarToText
            ? "underline"
            : "none",
          "--highlight-1": highlight1
            ? `rgb(${colorToString(parseColor(`hsba(${highlight1})`), "rgb")})`
            : "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 75%)",
          "--highlight-2": colorToString(highlight2, "rgb"),
          "--highlight-3": colorToString(highlight3, "rgb"),
          "--page-width-setting": pageWidth || 624,
          "--page-width-unitless": pageWidth || 624,
          "--page-width-units": `min(${pageWidth || 624}px, calc(100vw - 12px))`,
          "--theme-heading-font": headingFontValue,
          "--theme-font": bodyFontValue,
          "--theme-font-base-size": bodyFontBaseSize
            ? `${bodyFontBaseSize}px`
            : undefined,
        } as CSSProperties
      }
    >
      {" "}
      {children}{" "}
    </div>
  );
};

let CardThemeProviderContext = createContext<null | string>(null);
export function NestedCardThemeProvider(props: { children: React.ReactNode }) {
  let card = useContext(CardThemeProviderContext);
  if (!card) return props.children;
  return (
    <CardThemeProvider entityID={card}>{props.children}</CardThemeProvider>
  );
}

export function CardThemeProvider(props: {
  entityID: string;
  children: React.ReactNode;
}) {
  let bgPage = useColorAttributeNullable(
    props.entityID,
    "theme/card-background",
  );
  let primary = useColorAttributeNullable(props.entityID, "theme/primary");
  let accent1 = useColorAttributeNullable(
    props.entityID,
    "theme/accent-background",
  );
  let accent2 = useColorAttributeNullable(props.entityID, "theme/accent-text");
  let accentContrast =
    bgPage && accent1 && accent2
      ? [accent1, accent2].sort((a, b) => {
          let bgStr = colorToString(bgPage, "rgb");
          return (
            compareColors(colorToString(b, "rgb"), bgStr).distance -
            compareColors(colorToString(a, "rgb"), bgStr).distance
          );
        })[0]
      : null;

  return (
    <CardThemeProviderContext.Provider value={props.entityID}>
      <div
        className="contents text-primary"
        style={
          {
            "--accent-1": accent1 ? colorToString(accent1, "rgb") : undefined,
            "--accent-2": accent2 ? colorToString(accent2, "rgb") : undefined,
            "--accent-contrast": accentContrast
              ? colorToString(accentContrast, "rgb")
              : undefined,
            "--bg-page": bgPage ? colorToString(bgPage, "rgb") : undefined,
            "--bg-page-alpha": bgPage
              ? bgPage.getChannelValue("alpha")
              : undefined,
            "--primary": primary ? colorToString(primary, "rgb") : undefined,
          } as CSSProperties
        }
      >
        {props.children}
      </div>
    </CardThemeProviderContext.Provider>
  );
}

// Wrapper within the Theme Wrapper that provides background image data
export const ThemeBackgroundProvider = (props: {
  entityID: string;
  children: React.ReactNode;
}) => {
  let { data: pub, normalizedPublication } = useLeafletPublicationData();
  let backgroundImage = useEntity(props.entityID, "theme/background-image");
  let backgroundImageRepeat = useEntity(
    props.entityID,
    "theme/background-image-repeat",
  );
  if (pub?.publications) {
    return (
      <PublicationBackgroundProvider
        pub_creator={pub?.publications.identity_did || ""}
        theme={normalizedPublication?.theme}
      >
        {props.children}
      </PublicationBackgroundProvider>
    );
  }
  return (
    <div
      className="LeafletBackgroundWrapper w-full bg-bg-leaflet text-primary h-full flex flex-col bg-cover bg-center bg-no-repeat items-stretch"
      style={
        {
          backgroundImage: backgroundImage
            ? `url(${backgroundImage?.data.src}), url(${backgroundImage?.data.fallback})`
            : undefined,
          backgroundPosition: "center",
          backgroundRepeat: backgroundImageRepeat ? "repeat" : "no-repeat",
          backgroundSize: !backgroundImageRepeat
            ? "cover"
            : backgroundImageRepeat?.data.value,
        } as CSSProperties
      }
    >
      {props.children}
    </div>
  );
};
