import React, {
  createContext,
  ReactElement,
  useContext,
  useEffect,
  useState,
} from "react";

type BuiltInThemes = "light" | "dark";

export interface Theme {
  name: BuiltInThemes | "custom";
  palette: Palette;
}

export type SetThemeInput = BuiltInThemes | Partial<Palette>;

export const PALETTE_NAMES = [
  "--primary-background",
  "--secondary-background",

  "--primary-accent",
  "--primary-accent-soft",
  "--primary-accent-light",
  "--primary-accent-dim",

  "--secondary-accent",
  "--secondary-accent-light",

  "--primary-heading",
  "--secondary-heading",

  "--text",

  "--form-invalid",

  "--input-background",
  "--input-placeholder-text",
  "--input-text",

  "--navbar-page-overlay",
] as const;

export const emptyPalette = PALETTE_NAMES.reduce(
  (partial, varName) => ({
    ...partial,
    [varName]: "#c4e0f8",
  }),
  {} as Palette
);

const ThemeContext =
  createContext<
    | {
        theme: Theme;
        setTheme(input: SetThemeInput): void;
        save(): void;
        clearSaved(): void;
      }
    | undefined
  >(undefined);

export interface Props {
  children?: ReactElement;
}

export function ThemeProvider(props: Props) {
  const update = useForcedUpdate();
  const [themeName, setThemeName] =
    useState<Theme["name"] | undefined>(undefined);

  const setTheme = (input: SetThemeInput) => {
    if (typeof input === "string") {
      PALETTE_NAMES.forEach((name) =>
        document.body.style.setProperty(name, "")
      );

      if (input === "light") {
        document.body.classList.remove("dark");
      } else if (input === "dark") {
        document.body.classList.add("dark");
      }

      setThemeName(input);
    } else {
      const properties = Object.keys(input) as PaletteNames[];

      properties.forEach((property) =>
        document.body.style.setProperty(
          property,
          input[property]?.trim() ?? null
        )
      );

      setThemeName("custom");
    }

    update();
  };

  useEffect(() => {
    const customPalette = getSavedPalette();

    if (customPalette == null) {
      setThemeName("light");
    } else {
      setTheme(customPalette);
      setThemeName("custom");
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <ThemeContext.Provider
      value={
        themeName == null
          ? undefined
          : {
              theme: {
                name: themeName,
                get palette() {
                  return getCurrentPalette();
                },
              },
              setTheme,
              save: () => savePalette(getCurrentPalette()),
              clearSaved: clearSavedPalette,
            }
      }
    >
      {props.children}
    </ThemeContext.Provider>
  );
}

export function useThemeContext() {
  return useContext(ThemeContext);
}

export type PaletteNames = typeof PALETTE_NAMES[number];

export type Palette = {
  [key in PaletteNames]: string;
};

function getCurrentPalette() {
  const styles = getComputedStyle(document.body);

  return PALETTE_NAMES.reduce(
    (partial, varName) => ({
      ...partial,
      [varName]: styles.getPropertyValue(varName).trim(),
    }),
    {} as Palette
  );
}

function useForcedUpdate() {
  const [fakeState, setFakeState] = useState(true);

  return () => setFakeState(!fakeState);
}

const STORAGE_KEY = "csc-theme-palette";

function getSavedPalette() {
  const raw = localStorage.getItem(STORAGE_KEY);

  return raw == null ? undefined : (JSON.parse(raw) as Palette);
}

function savePalette(palette: Palette) {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(palette));
}

function clearSavedPalette() {
  localStorage.removeItem(STORAGE_KEY);
}
