import {
  alpha,
  createTheme,
  responsiveFontSizes,
  type Theme,
  type ThemeOptions,
  ThemeProvider as MaterialUiThemeProvider,
  type TypographyStyle,
  type CSSObject,
} from '@mui/material/styles';
import { observer } from 'mobx-react-lite';
import { type ReactNode, createContext, useContext } from 'react';

import { useRootStore } from '../RootStoreProvider';

interface OuterPalette {
  background: string;
  border: string;
}

interface HighlightPalette {
  number: string;
  parameter: string;
  comment: string;
  activeLine: string;
  selection: string;
  foldPlaceholder: string;
  activeLintRange: string;
  occurences: {
    read: string;
    write: string;
  };
  search: {
    match: string;
    selected: string;
    contrastText: string;
  };
}

declare module '@mui/material/styles' {
  interface TypographyVariants {
    fontWeightEditorNormal: number;
    fontWeightEditorBold: number;
    editor: TypographyStyle;
  }

  interface TypographyVariantsOptions {
    fontWeightEditorNormal?: number;
    fontWeightEditorBold?: number;
    editor?: TypographyStyle;
  }

  interface Palette {
    outer: OuterPalette;
    highlight: HighlightPalette;
  }

  interface PaletteOptions {
    outer?: Partial<OuterPalette>;
    highlight?: Partial<HighlightPalette>;
  }
}

function createResponsiveTheme(
  options: ThemeOptions,
  overrides: ThemeOptions = {},
): Theme {
  const theme = createTheme({
    ...options,
    typography: {
      fontFamily:
        '"InterVariable", "Inter", "Roboto", "Helvetica", "Arial", sans-serif',
      fontWeightMedium: 600,
      fontWeightEditorNormal: 400,
      fontWeightEditorBold: 700,
      button: {
        // 24px line height for 14px button text to fix browser rounding errors.
        lineHeight: 1.714286,
      },
      editor: {
        fontFamily:
          '"JetBrains MonoVariable", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace',
        fontFeatureSettings: '"liga", "calt"',
        // `rem` for JetBrains MonoVariable make the text too large in Safari.
        fontSize: '16px',
        fontWeight: 400,
        lineHeight: 1.5,
        letterSpacing: 0,
        textRendering: 'optimizeLegibility',
      },
      ...(options.typography ?? {}),
    },
  });

  function shadedButtonStyle(color: string): CSSObject {
    const opacity = theme.palette.action.focusOpacity;
    return {
      background: alpha(color, opacity),
      ':hover': {
        background: alpha(color, opacity + theme.palette.action.hoverOpacity),
        '@media(hover: none)': {
          background: alpha(color, opacity),
        },
      },
      '&.Mui-disabled': {
        background: alpha(theme.palette.text.disabled, opacity),
      },
    };
  }

  const themeWithComponents = createTheme(theme, {
    components: {
      MuiCssBaseline: {
        styleOverrides: {
          body: {
            overscrollBehavior: 'contain',
          },
        },
      },
      MuiButton: {
        styleOverrides: {
          root: {
            '&.rounded': { borderRadius: '50em' },
            '.MuiButton-startIcon': { marginRight: 6 },
            '.MuiButton-endIcon': { marginLeft: 6 },
            '&.shaded': {
              ...shadedButtonStyle(theme.palette.text.primary),
              ...(
                [
                  'primary',
                  'secondary',
                  'error',
                  'warning',
                  'success',
                  'info',
                ] as const
              ).reduce((accumulator: CSSObject, color) => {
                const colorCapitalized =
                  (color[0] ?? '').toUpperCase() + color.substring(1);
                return {
                  ...accumulator,
                  [`&.MuiButton-text${colorCapitalized}, &.MuiButton-outlined${colorCapitalized}`]:
                    shadedButtonStyle(theme.palette[color].main),
                };
              }, {}),
            },
          },
          sizeSmall: { fontSize: '0.75rem' },
          sizeLarge: { fontSize: '1rem' },
          text: { '&.rounded': { padding: '6px 14px' } },
          textSizeSmall: { '&.rounded': { padding: '4px 8px' } },
          textSizeLarge: { '&.rounded': { padding: '8px 20px' } },
          outlined: { '&.rounded': { padding: '5px 13px' } },
          outlinedSizeSmall: { '&.rounded': { padding: '3px 9px' } },
          outlinedSizeLarge: { '&.rounded': { padding: '7px 19px' } },
        },
      },
      MuiToggleButton: {
        styleOverrides: {
          root: { '&.iconOnly': { borderRadius: '100%' } },
        },
      },
      MuiToggleButtonGroup: {
        styleOverrides: {
          root: {
            '&.rounded .MuiToggleButtonGroup-groupedHorizontal': {
              ':first-of-type': {
                paddingLeft: 15,
                borderRadius: '50em 0 0 50em',
              },
              ':last-of-type': {
                paddingRight: 15,
                borderRadius: '0 50em 50em 0',
              },
              '&.MuiToggleButton-sizeSmall': {
                ':first-of-type': { paddingLeft: 9 },
                ':last-of-type': { paddingRight: 9 },
              },
              '&.MuiToggleButton-sizeLarge': {
                ':first-of-type': { paddingLeft: 21 },
                ':last-of-type': { paddingRight: 21 },
              },
            },
          },
        },
      },
      MuiTooltip: {
        styleOverrides: {
          tooltip: {
            background: alpha('#212121', 0.93),
            color: '#fff',
          },
          arrow: {
            color: alpha('#212121', 0.93),
          },
        },
      },
    },
  });

  const themeWithOverrides = createTheme(themeWithComponents, overrides);

  return responsiveFontSizes(themeWithOverrides);
}

const lightTheme = (() => {
  const primaryText = '#19202b';
  const disabledText = '#a0a1a7';
  const darkBackground = '#f5f5f5';

  return createResponsiveTheme({
    palette: {
      mode: 'light',
      primary: { main: '#038a99' },
      secondary: { main: '#e45649' },
      error: { main: '#ca1243' },
      warning: { main: '#c18401' },
      success: { main: '#50a14f' },
      info: { main: '#4078f2' },
      background: {
        default: '#fff',
        paper: '#fff',
      },
      text: {
        primary: primaryText,
        secondary: '#696c77',
        disabled: disabledText,
      },
      divider: alpha(primaryText, 0.16),
      outer: {
        background: darkBackground,
        border: '#c8c8c8',
      },
      highlight: {
        number: '#0084bc',
        parameter: '#6a3e3e',
        comment: disabledText,
        activeLine: darkBackground,
        selection: '#c8e4fb',
        foldPlaceholder: alpha(primaryText, 0.08),
        activeLintRange: alpha('#f2a60d', 0.28),
        occurences: {
          read: alpha(primaryText, 0.16),
          write: alpha(primaryText, 0.16),
        },
        search: {
          match: '#00bcd4',
          selected: '#d500f9',
          contrastText: '#fff',
        },
      },
    },
  });
})();

const darkTheme = (() => {
  const primaryText = '#ebebff';
  const secondaryText = '#abb2bf';
  const darkBackground = '#21252b';

  return createResponsiveTheme(
    {
      typography: {
        fontWeightEditorNormal: 350,
        fontWeightEditorBold: 650,
      },
      palette: {
        mode: 'dark',
        primary: { main: '#56b6c2' },
        secondary: { main: '#be5046' },
        error: { main: '#e06c75' },
        warning: { main: '#e5c07b' },
        success: { main: '#98c379' },
        info: { main: '#61afef' },
        background: {
          default: '#282c34',
          paper: darkBackground,
        },
        text: {
          primary: primaryText,
          secondary: secondaryText,
          disabled: '#5c6370',
        },
        divider: alpha(secondaryText, 0.24),
        outer: {
          background: darkBackground,
          border: '#181a1f',
        },
        highlight: {
          number: '#6188a6',
          parameter: '#c8ae9d',
          comment: '#7f848e',
          activeLine: '#2c313c',
          selection: '#404859',
          foldPlaceholder: alpha(primaryText, 0.12),
          activeLintRange: alpha('#fbc346', 0.28),
          occurences: {
            read: alpha(primaryText, 0.14),
            write: alpha(primaryText, 0.14),
          },
          search: {
            match: '#33eaff',
            selected: '#dd33fa',
            contrastText: darkBackground,
          },
        },
      },
    },
    {
      components: {
        MuiTooltip: {
          styleOverrides: {
            tooltip: {
              color: primaryText,
            },
          },
        },
      },
    },
  );
})();

const ContrastThemeContext = createContext<Theme | undefined>(undefined);

function ThemeAndContrastThemeProvider({
  theme,
  contrastTheme,
  children,
}: {
  theme: Theme;
  contrastTheme: Theme;
  children?: ReactNode;
}): JSX.Element {
  return (
    <MaterialUiThemeProvider theme={theme}>
      <ContrastThemeContext.Provider value={contrastTheme}>
        {children}
      </ContrastThemeContext.Provider>
    </MaterialUiThemeProvider>
  );
}

ThemeAndContrastThemeProvider.defaultProps = {
  children: undefined,
};

export function ContrastThemeProvider({
  children,
}: {
  children?: ReactNode;
}): JSX.Element {
  const contrastTheme = useContext(ContrastThemeContext);
  if (!contrastTheme) {
    throw new Error('ContrastThemeProvider must be used within ThemeProvider');
  }
  return (
    <MaterialUiThemeProvider theme={contrastTheme}>
      {children}
    </MaterialUiThemeProvider>
  );
}

ContrastThemeProvider.defaultProps = {
  children: undefined,
};

const ThemeProvider = observer(function ThemeProvider({
  children,
}: {
  children?: ReactNode;
}): JSX.Element {
  const {
    themeStore: { darkMode },
  } = useRootStore();

  return (
    <ThemeAndContrastThemeProvider
      theme={darkMode ? darkTheme : lightTheme}
      contrastTheme={darkTheme}
    >
      {children}
    </ThemeAndContrastThemeProvider>
  );
});

ThemeProvider.defaultProps = {
  children: undefined,
};

export default ThemeProvider;
