import PropTypes from 'prop-types';
import classnames from 'classnames';
import IfActive from "../../application/IfActive/index.js";
import { NATIONAL_SIZE } from '../sizes';
import useZoom from '../hooks/useZoom';
import { ZoomContext } from '../hooks/useZoomContext';
import InstructionOverlay from '../InstructionOverlay';
import Controls from './Controls';
import styles from './styles.module.scss';

/**
 * Renders an SVG canvas with content childen. Provides a zoom context for
 * children to consume. Handles zoom interactions if zooming is enabled with
 * the `zoomable` prop. The zooming behavior can be controlled by calling the
 * `useZoom` hook in a parent component and passing the resulting zoom value
 * into a `Canvas` as the `zoom` prop. When using a controlled `Canvas`,
 * some configuration props are ignored in favor of the configuration provided
 * in the controlled zoom value.
 */
const Canvas = function Canvas(props) {
  const {
    children,
    zoom,
    viewExtent: propViewExtent,
    size: propSize,
    zoomable: propZoomable,
    scaleMax,
    zoomBy,
    customClasses,
    ...restProps
  } = props;
  const zoomContext = useZoom({
    size: propSize,
    viewExtent: propViewExtent,
    zoomable: propZoomable,
    scaleMax,
    zoomBy
  }, zoom);
  const {
    containerRef,
    canvasRef,
    transform,
    zoomIn,
    zoomOut,
    config: {
      zoomable,
      viewExtent
    },
    instructionOverlay,
    clearInstructionOverlay
  } = zoomContext;
  const [[viewboxX0, viewboxY0], [viewboxX1, viewboxY1]] = viewExtent;
  const viewWidth = viewboxX1 - viewboxX0;
  const viewHeight = viewboxY1 - viewboxY0;
  const viewBox = [viewboxX0, viewboxY0, viewWidth, viewHeight];
  const svgTransform = `translate(${transform.x}, ${transform.y})` + ` scale(${transform.k})`;
  const {
    container: customContainerClass,
    mapContainer: customMapContainerClass,
    canvas: customCanvasClass
  } = customClasses;
  return <ZoomContext.Provider value={zoomContext}>
      <div className={classnames(styles.container, customContainerClass)}>
        <InstructionOverlay instructionOverlay={instructionOverlay} clearInstructionOverlay={clearInstructionOverlay} />
        <IfActive active={zoomable}>
          <Controls zoomIn={zoomIn} zoomOut={zoomOut} />
        </IfActive>
        <div ref={containerRef} style={{
        aspectRatio: viewWidth / viewHeight
      }} className={classnames(styles.mapContainer, customMapContainerClass)}>
          <svg ref={canvasRef} viewBox={viewBox} className={customCanvasClass} {...restProps}>
            <g transform={svgTransform} strokeWidth={1 / (2 * transform.k)}>
              {children}
            </g>
          </svg>
        </div>
      </div>
    </ZoomContext.Provider>;
};
Canvas.defaultProps = {
  zoom: undefined,
  size: NATIONAL_SIZE,
  viewExtent: undefined,
  zoomable: false,
  zoomBy: 2,
  scaleMax: 8,
  customClasses: {},
  children: undefined
};
Canvas.propTypes = {
  /**
   * A zoom object as returned by the `useZoom` hook. See the hook for
   * documentation on the shape of this value; for the purposes of this prop
   * you should just pass in the value you get from the hook.
   */
  zoom: PropTypes.shape({}),
  /**
   * The `[w,h]` size of the canvas in SVG coordinate space. This defines the
   * view box of the SVG canvas, and, unless `viewSize` is specified, the
   * aspect ratio at which the canvas will be displayed on screen.
   *
   * For national maps this should generally be the default of `[960, 500]` and
   * for state maps it should generally be `[600, 400]`; it should map the
   * coordinate space your TopoJSON is pre-projected into.
   *
   * If a controlled `zoom` is provided, this size is ignored and we use the
   * size dictated by the controlled zoom value.
   */
  size: PropTypes.arrayOf(PropTypes.number),
  /**
   * This `[[x0,y0], [x1,y1]]` extent of the view into the canvas. This can be
   * used to clip the view into the map to a narrower slice (say, for mobile)
   * which you can still pan around. If this is not specified then we use `[[0,
   * 0], size]`, which would generally make the whole canvas visible.
   */
  viewExtent: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)),
  /**
   * Set this to `false` if you want to prevent zooming functionality. Note
   * that if this value gets changed during the lifespan of a Canvas component,
   * we reset the zoom to show the initial state of the canvas.
   *
   * If a controlled `zoom` is provided, this value is ignored and we use the
   * zoomability dictated by the controlled zoom value.
   */
  zoomable: PropTypes.bool,
  /**
   * The multiplicative zoom factor used by the zoom-in/zoom-out buttons. For
   * example, a value of 2 means that the canvas will be scaled by a factor of
   * 2 each time the user clicks "zoom in".
   *
   * If a controlled `zoom` is provided, this value is ignored and we use the
   * zoom-by factor of the controlled zoom value.
   */
  zoomBy: PropTypes.number,
  /**
   * The maximum multiplicative zoom factor that the canvas allows the user to
   * zoom to.
   *
   * If a controlled `zoom` is provided, this value is ignored and we use the
   * maximum scale of the controlled zoom value.
   */
  scaleMax: PropTypes.number,
  /**
   * Custom CSS classes to style the canvas and container elements.
   */
  customClasses: PropTypes.shape({
    container: PropTypes.string,
    mapContainer: PropTypes.string,
    canvas: PropTypes.string
  }),
  /**
   * This component's children should render the actual content of the SVG,
   * using the provided zoom context as necessary.
   */
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)])
};
export default Canvas;