import { Box as MUIBox, BoxProps as MUIBoxProps } from '@mui/material';
import { FC, useMemo } from 'react';

type FlexBoxDirection = 'column' | 'row';

type FlexBoxSpacing = number | string;

type FlexBoxOptionalPropsWithDefault = {
  fullWidth?: boolean;
  growFirst?: boolean;
  row?: boolean;
  spacing?: FlexBoxSpacing;
  xs?: FlexBoxDirection;
};

type FlexBoxOptionalPropsWithoutDefault = {
  columnSpacing?: FlexBoxSpacing;
  rowSpacing?: FlexBoxSpacing;
  lg?: FlexBoxDirection;
  md?: FlexBoxDirection;
  sm?: FlexBoxDirection;
  xl?: FlexBoxDirection;
};

type FlexBoxOptionalProps = FlexBoxOptionalPropsWithDefault &
  FlexBoxOptionalPropsWithoutDefault;

type FlexBoxProps = MUIBoxProps & FlexBoxOptionalProps;

const FLEX_BOX_DEFAULT_PROPS: Required<FlexBoxOptionalPropsWithDefault> &
  FlexBoxOptionalPropsWithoutDefault = {
  columnSpacing: undefined,
  fullWidth: false,
  growFirst: false,
  row: false,
  rowSpacing: undefined,
  lg: undefined,
  md: undefined,
  sm: undefined,
  spacing: '1em',
  xl: undefined,
  xs: 'column',
};

const FlexBox: FC<FlexBoxProps> = ({
  fullWidth,
  growFirst,
  lg: dLg = FLEX_BOX_DEFAULT_PROPS.lg,
  md: dMd = FLEX_BOX_DEFAULT_PROPS.md,
  row: isRow,
  sm: dSm = FLEX_BOX_DEFAULT_PROPS.sm,
  spacing = FLEX_BOX_DEFAULT_PROPS.spacing,
  sx,
  xl: dXl = FLEX_BOX_DEFAULT_PROPS.xl,
  xs: dXs = FLEX_BOX_DEFAULT_PROPS.xs,
  // Input props that depend on other input props.
  columnSpacing = spacing,
  rowSpacing = spacing,
  ...muiBoxRestProps
}) => {
  const xs = useMemo(() => (isRow ? 'row' : dXs), [dXs, isRow]);
  const sm = useMemo(() => dSm || xs, [dSm, xs]);
  const md = useMemo(() => dMd || sm, [dMd, sm]);
  const lg = useMemo(() => dLg || md, [dLg, md]);
  const xl = useMemo(() => dXl || lg, [dXl, lg]);

  const mapToSx: Record<
    FlexBoxDirection,
    {
      alignItems: string;
      marginLeft: FlexBoxSpacing;
      marginTop: FlexBoxSpacing;
    }
  > = useMemo(
    () => ({
      column: {
        alignItems: 'normal',
        marginLeft: 0,
        marginTop: columnSpacing,
      },
      row: {
        alignItems: 'center',
        marginLeft: rowSpacing,
        marginTop: 0,
      },
    }),
    [columnSpacing, rowSpacing],
  );
  const firstChildFlexGrow = useMemo(
    () => (growFirst ? 1 : undefined),
    [growFirst],
  );
  const width = useMemo(() => (fullWidth ? '100%' : undefined), [fullWidth]);

  return (
    <MUIBox
      {...{
        ...muiBoxRestProps,
        sx: {
          alignItems: {
            xs: mapToSx[xs].alignItems,
            sm: mapToSx[sm].alignItems,
            md: mapToSx[md].alignItems,
            lg: mapToSx[lg].alignItems,
            xl: mapToSx[xl].alignItems,
          },
          display: 'flex',
          flexDirection: { xs, sm, md, lg, xl },
          width,

          '& > :first-child': {
            flexGrow: firstChildFlexGrow,
          },

          '& > :not(:first-child)': {
            marginLeft: {
              xs: mapToSx[xs].marginLeft,
              sm: mapToSx[sm].marginLeft,
              md: mapToSx[md].marginLeft,
              lg: mapToSx[lg].marginLeft,
              xl: mapToSx[xl].marginLeft,
            },
            marginTop: {
              xs: mapToSx[xs].marginTop,
              sm: mapToSx[sm].marginTop,
              md: mapToSx[md].marginTop,
              lg: mapToSx[lg].marginTop,
              xl: mapToSx[xl].marginTop,
            },
          },

          ...sx,
        },
      }}
    />
  );
};

FlexBox.defaultProps = FLEX_BOX_DEFAULT_PROPS;

export type { FlexBoxProps };

export default FlexBox;
