import { forwardRef } from 'react';
import PropTypes from 'prop-types';
import useResponsiveValues from "../../../hooks/breakpoint/useResponsiveValues/index.js";
import CustomPropTypes from "../../../utils/prop-types/index.js";
import cx from "../../../utils/css/cx.js";
import getMarginClasses from "../../../utils/css/getMarginClasses.js";
import getBreakpointClasses from './utils/getBreakpointClasses';
import classes from './styles.module.scss';

/**
 * A single block of content positioned within a grid (usually provided by the
 * `Grid` component). By default a `GridItem` will share horizontal space in
 * the grid with its siblings, but an item can also specify how many columns it
 * should take up at various breakpoints. An item's breakpoint behaviors apply
 * upwards, so if you only specify column behaviors at the `xs` and `lg`
 * breakpoints, the `xs` behavior will apply until you reach the `lg`
 * breakpoint. If you choose to configure one or more breakpoints, but _not_
 * the `xs` breakpoint, then the item will take up the full horizontal space
 * available in the grid until you reach the lowest configured breakpoint.
 *
 * Each individual breakpoint can be configured in a few different ways. First,
 * you can specify the number of columns that the item should take up at or
 * above the breakpoint, either as a number or as a string. The first example
 * here will take up six columns out of the 12 available in our standard grid
 * at all breakpoints.
 *
 * ```jsx
 *
 * <GridItem xs="6" />
 * ```
 *
 * This next example will occupy the full width of the grid until the screen
 * reaches the `md` breakpoint, at which point it will switch to occupying only
 * four of the 12 available columns.
 *
 * ```jsx
 *
 * <GridItem md="4" />
 * ```
 *
 * You can also set a breakpoint to the value `true` rather than a number of
 * columns, which will cause it to share horizontal space equitably with its
 * siblings in the grid. This is useful if you want to utilize an item's
 * default behavior at low breakpoints but then configure its column width at
 * higher breakpoints.
 *
 * ```jsx
 *
 * <GridItem xs lg="8" />
 * ```
 *
 * Finally, if necessary you can also specify a column offset by configuring
 * a breakpoint with an object with `columns` and `offset` keys.
 *
 * ```jsx
 *
 * <GridItem xs={{ columns: 4, offset: 8 }} />
 * ```
 *
 * You can also change which kind of tag or component is rendered by the
 * `GridItem` component with the `as` prop.
 *
 * ```js
 *
 * import { GridItem } from '@politico/interactive-style';
 * ```
 */
const GridItem = forwardRef((props, ref) => {
  const {
    children,
    as: Tag,
    xs,
    sm,
    md,
    lg,
    xl,
    margin,
    relative
  } = props;
  const marginValue = useResponsiveValues(margin);
  const margins = getMarginClasses(marginValue, {
    defaultSide: 'y',
    sides: ['y', 't', 'b']
  });
  const breakpointClasses = getBreakpointClasses({
    xs,
    sm,
    md,
    lg,
    xl
  });
  return <Tag ref={ref} className={cx.merge(classes)('root', breakpointClasses, margins, {
    relative
  })}>
      {children}
    </Tag>;
});
GridItem.displayName = 'GridItem';
GridItem.defaultProps = {
  as: 'div',
  xs: undefined,
  sm: undefined,
  md: undefined,
  lg: undefined,
  xl: undefined,
  margin: undefined,
  relative: false,
  children: undefined
};
GridItem.propTypes = {
  /**
   * Control which kind of tag or component is used to render the item
   * container. If you use a custom component it has to accept a `className`
   * prop.
   */
  as: PropTypes.elementType,
  xs: PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number, PropTypes.exact({
    columns: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    offset: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
  })]),
  sm: PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number, PropTypes.exact({
    columns: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    offset: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
  })]),
  md: PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number, PropTypes.exact({
    columns: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    offset: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
  })]),
  lg: PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number, PropTypes.exact({
    columns: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    offset: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
  })]),
  xl: PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number, PropTypes.exact({
    columns: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    offset: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
  })]),
  /**
   * The vertical margins of the grid item. Margins can be specified as one
   * spacing value that gets used on both top and bottom, an explicit `y`
   * value, or completely separate `tb` values. The values correspond to levels
   * of our spacing scale (e.g., `"4"` corresponds to `$spacing-4`). Note that
   * grid items do not support custom horizontal margins.
   */
  margin: CustomPropTypes.responsive(PropTypes.oneOfType([PropTypes.string, PropTypes.shape({
    y: PropTypes.string
  }), PropTypes.shape({
    t: PropTypes.string,
    b: PropTypes.string
  })])),
  /**
   * Whether the grid item should specify `position: relative` to allow
   * absolute positioning within it.
   */
  relative: PropTypes.bool,
  /**
   * Any children that should be rendered inside this content block in the
   * grid.
   */
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)])
};
export default GridItem;