text: 'use strict';
  
  import * as d3 from 'd3';
  import { Shape } from '../../../core';
  import {
    getInterpolationType,
    parseTypedValue,
  } from '../../../core/BaseConfig';
  import { getDefaultSVGProps } from '../../../core/Shape';
  import {
    calculateVerticalPadding,
    getXAxisXPosition,
    isValidAxisType,
  } from '../../../helpers/axis';
  import constants, { SHAPES } from '../../../helpers/constants';
  import errors from '../../../helpers/errors';
  import {
    legendClickHandler,
    legendHoverHandler,
    loadLegendItem,
    isLegendSelected,
  } from '../../../helpers/legend';
  import {
    processRegions,
    regionLegendHoverHandler,
  } from '../../../helpers/region';
  import { getSVGObject } from '../../../helpers/shapeSVG';
  import styles from '../../../helpers/styles';
  import { getTransformScale } from '../../../helpers/transformUtils';
  import utils from '../../../helpers/utils';
  import {
    d3RemoveElement,
    getColorForTarget,
    getShapeForTarget,
  } from '../../Graph/helpers/helpers';
  
  /**
   * @typedef Scatter
   */
  /**
   * Transforms the points in the Scatter graph on resize
   *
   * @private
   * @param {object} scale - d3 scale for Graph
   * @returns {Function} - translate function for d3 transform
   */
  const transformPoint = (scale) => (value) => (scaleFactor) => {
    const getX = (val) => scale.x(val.x);
    const getY = (val) => scale[val.yAxis](val.y);
    return `translate(${getX(value)},${getY(value)}) scale(${scaleFactor})`;
  };
  /**
   * Transforms points for a data point set in the Scatter graph on resize
   *
   * @private
   * @param {object} scale - d3 scale for Graph
   * @param {d3.selection} canvasSVG - d3 selection node of canvas svg
   * @param {string} cls - selector for the data point translation
   * @param {object} config - config object derived from input JSON
   * @returns {object} d3 select object
   */
  const translatePoints = (scale, canvasSVG, cls, config) => canvasSVG
    .selectAll(`.${styles.scatterGraphContent} .${cls}`)
    .each(function (d) {
      const pointSVG = d3.select(this);
      pointSVG
        .select('g')
        .transition()
        .call(
          constants.d3Transition(config.settingsDictionary.transition),
        )
        .attr('transform', function () {
          return transformPoint(scale)(d)(getTransformScale(this));
        });
    });
  /**
   * Toggles the selection of a data point, executes on click of a data point.
   *
   * @private
   * @param {object} target - DOM element of the data point clicked
   * @returns {Array} d3 html element of the selected point
   */
  const toggleDataPointSelection = (target) => {
    const selectedPointNode = d3
      .select(target.parentNode)
      .select(`.${styles.dataPointSelection}`);
    selectedPointNode.attr(
      'aria-hidden',
      !(selectedPointNode.attr('aria-hidden') === 'true'),
    );
    return selectedPointNode;
  };
  /**
   * Handler for the data point on click. If the content property is present for the data point
   * then the callback is executed other wise it is NOP.
   * If the callback is present, the selected data point is toggled and the element is passed as an argument to the
   * consumer in the callback, to execute once the popup is closed.
   *  Callback arguments:
   *      Post close callback function
   *      value [x and y data point values]
   *      Selected data point target [d3 target]
   *  On close of popup, call -> the provided callback
   *
   * @private
   * @param {object} value - data point object
   * @param {number} index - data point index for the set
   * @param {object} target - DOM object of the clicked point
   * @returns {undefined} - returns nothing
   */
  const dataPointActionHandler = (value, index, target) => {
    if (utils.isEmpty(value.onClick)) {
      return;
    }
    toggleDataPointSelection(target).call((selectedTarget) => value.onClick(
      () => {
        selectedTarget.attr('aria-hidden', true);
      },
      value.key,
      index,
      value,
      selectedTarget,
    ));
  };
  /**
   * Called on resize, translates the data point values.
   * This includes:
   *  Points
   *  Selected point indicators
   *
   * @private
   * @param {object} scale - d3 scale for Graph
   * @param {d3.selection} canvasSVG - d3 selection node of canvas svg
   * @param {object} config - config object derived from input JSON
   * @returns {undefined} - returns nothing
   */
  const translateScatterGraph = (scale, canvasSVG, config) => {
    translatePoints(scale, canvasSVG, styles.point, config);
    translatePoints(scale, canvasSVG, styles.dataPointSelection, config);
  };
  /**
   * Returns the internal values subset which is the array that was created from the input JSON.
   * This array has information for each data point w.r.t shape, colors and on click callback along with
   * x and y co-ordinates.
   *
   * @private
   * @param {object} target - Object containing the subsets
   * @returns {Array} List of data point subsets
   */
  const getDataPointValues = (target) => target.internalValuesSubset;
  /**
   * Draws the points with options opted in the input JSON by the consumer for each data set.
   *  Render the point with appropriate color, shape, x and y co-ordinates, label etc.
   *  On click content callback function is called.
   *
   * @private
   * @param {object} scale - d3 scale for Graph
   * @param {object} config - Graph config object derived from input JSON
   * @param {Array} pointGroupPath - d3 html element of the points group
   * @returns {undefined} - returns nothing
   */
  const drawDataPoints = (scale, config, pointGroupPath) => {
    const renderDataPointPath = (path, value, index) => path.append(() => new Shape(getShapeForTarget(value)).getShapeElement(
      getDefaultSVGProps({
        svgClassNames: styles.point,
        svgStyles: `fill: ${getColorForTarget(value)};`,
        transformFn: transformPoint(scale)(value),
        onClickFn() {
          dataPointActionHandler(value, index, this);
        },
        a11yAttributes: {
          'aria-hidden':
            document
              .querySelector(
                `.${styles.legendItem}[aria-describedby="${value.key}"]`,
              )
              ?.getAttribute('aria-current') === 'false',
          'aria-describedby': value.key,
          'aria-disabled': !utils.isFunction(value.onClick),
        },
      }),
    ));
    const renderSelectionPath = (path, value, index) => path.append(() => new Shape(
      getSVGObject(
        SHAPES.CIRCLE,
        constants.DEFAULT_PLOT_SELECTION_SCALE,
      ),
    ).getShapeElement(
      getDefaultSVGProps({
        svgClassNames: styles.dataPointSelection,
        transformFn: transformPoint(scale)(value),
        onClickFn() {
          dataPointActionHandler(value, index, this);
        },
        a11yAttributes: {
          'aria-hidden': true,
          'aria-describedby': value.key,
          'aria-disabled': !utils.isFunction(value.onClick),
        },
      }),
    ));
    const renderCriticalityPath = (path, value, index, cls) => path.append(() => new Shape(getShapeForTarget(value)).getShapeElement(
      getDefaultSVGProps({
        svgClassNames: `${styles.point} ${cls}`,
        transformFn: transformPoint(scale)(value),
        onClickFn() {
          dataPointActionHandler(value, index, this);
        },
        a11yAttributes: {
          'aria-hidden':
            document
              .querySelector(
                `.${styles.legendItem}[aria-describedby="${value.key}"]`,
              )
              ?.getAttribute('aria-current') === 'false',
          'aria-describedby': value.key,
          'aria-disabled': !utils.isFunction(value.onClick),
        },
      }),
    ));
    pointGroupPath
      .append('g')
      .classed(styles.pointGroup, true)
      .each(function (d, i) {
        const dataPointSVG = d3.select(this);
        renderSelectionPath(dataPointSVG, d, i);
        if (d.isCritical) {
          config.hasCriticality = true;
          renderCriticalityPath(
            dataPointSVG,
            d,
            i,
            styles.criticalityOuterPoint,
          );
          renderCriticalityPath(
            dataPointSVG,
            d,
            i,
            styles.criticalityInnerPoint,
          );
        }
        renderDataPointPath(dataPointSVG, d, i);
      });
  };
  /**
   * Draws the Scatter graph on the canvas element. This calls the Graph API to render the following first
   *  Grid
   *  Axes
   *  Legend
   *  Labels
   * Once these items are rendered, we will parse through the data points and render the points
   *
   * @private
   * @param {object} scale - d3 scale taking into account the input parameters
   * @param {object} config - config object derived from input JSON
   * @param {d3.selection} canvasSVG - d3 selection node of canvas svg
   * @param {Array} dataTarget - Data points
   * @returns {undefined} - returns nothing
   */
  const draw = (scale, config, canvasSVG, dataTarget) => {
    const scatterSVG = canvasSVG
      .append('g')
      .classed(styles.scatterGraphContent, true)
      .attr('clip-path', `url(#${config.clipPathId})`)
      .attr('aria-hidden', config.shownTargets.indexOf(dataTarget.key) < 0)
      .attr('aria-describedby', dataTarget.key);
    const currentPointsPath = scatterSVG
      .selectAll(`.${styles.currentPointsGroup}`)
      .data([dataTarget]);
    currentPointsPath
      .enter()
      .append('g')
      .classed(styles.currentPointsGroup, true)
      .attr(
        'transform',
              `translate(${getXAxisXPosition(config)},${calculateVerticalPadding(
                config,
              )})`,
      );
    currentPointsPath
      .exit()
      .transition()
      .call(constants.d3Transition(config.settingsDictionary.transition))
      .remove();
    const pointPath = scatterSVG
      .select(`.${styles.currentPointsGroup}`)
      .selectAll(`.${styles.point}`)
      .data(getDataPointValues(dataTarget).filter((d) => d.y !== null));
    drawDataPoints(scale, config, pointPath.enter());
    pointPath
      .exit()
      .transition()
      .call(constants.d3Transition(config.settingsDictionary.transition))
      .remove();
  };
  /**
   * Processes the input JSON and adds the shapes, colors, labels etc. to each data points so that we
   * can use them when rendering the data point.
   *
   * @private
   * @param {object} graphConfig - config object of Graph API
   * @param {object} dataTarget - Data points object
   * @returns {object} dataTarget - Updated data target object
   */
  const processDataPoints = (graphConfig, dataTarget) => {
    const { type } = graphConfig.axis.x;
    const getXDataValues = (x) => {
      if (!isValidAxisType(x, type)) {
        throw new Error(errors.THROW_MSG_INVALID_FORMAT_TYPE);
      }
      return parseTypedValue(x, type);
    };
      // Update the interpolation type
    dataTarget.interpolationType = getInterpolationType(dataTarget.type);
    dataTarget.showShapes = true;
    graphConfig.shownTargets.push(dataTarget.key);
    dataTarget.internalValuesSubset = dataTarget.values.map((value) => ({
      onClick: dataTarget.onClick,
      isCritical: value.isCritical || false,
      x: getXDataValues(value.x),
      y: value.y,
      color: dataTarget.color || constants.DEFAULT_COLOR,
      label: dataTarget.label || {},
      shape: dataTarget.shape || SHAPES.CIRCLE,
      yAxis: dataTarget.yAxis || constants.Y_AXIS,
      key: dataTarget.key,
    }));
    return dataTarget;
  };
  /**
   * Handler for Request animation frame, executes on resize.
   * * Order of execution
   * * Shows/hides the regions
   *
   * @private
   * @param {object} graphContext - graph object
   * @param {object} config - Graph config object derived from input JSON
   * @param {d3.selection} canvasSVG - d3 selection node of canvas svg
   * @returns {function()} callback function handler for RAF
   */
  const onAnimationHandler = (graphContext, config, canvasSVG) => () => {
    processRegions(graphContext, config, canvasSVG);
  };
  /**
   * Click handler for legend item. Removes the data points from scatter graph when clicked
   *
   * @private
   * @param {object} graphContext - Graph instance
   * @param {Scatter} control - Scatter instance
   * @param {object} config - Graph config object derived from input JSON
   * @param {d3.selection} canvasSVG - d3 selection node of canvas svg
   * @returns {Function} - returns callback function that handles click action on legend item
   */
  const clickHandler = (graphContext, control, config, canvasSVG) => (
    element,
    item,
  ) => {
    const updateShownTarget = (shownTargets, i) => {
      const index = shownTargets.indexOf(i.key);
      if (index > -1) {
        shownTargets.splice(index, 1);
      } else {
        shownTargets.push(i.key);
      }
    };
    legendClickHandler(element);
    const legendSelected = isLegendSelected(d3.select(element));
    updateShownTarget(config.shownTargets, item);
    canvasSVG
      .selectAll(
              `.${styles.dataPointSelection}[aria-describedby="${item.key}"]`,
      )
      .attr('aria-hidden', true);
    canvasSVG
      .selectAll(`path[aria-describedby="${item.key}"]`)
      .attr('aria-hidden', legendSelected);
    canvasSVG
      .selectAll(`.${styles.point}[aria-describedby="${item.key}"]`)
      .attr('aria-hidden', legendSelected);
    window.requestAnimationFrame(
      onAnimationHandler(graphContext, config, canvasSVG),
    );
  };
  /**
   * Hover handler for legend item. Highlights current scatter data points and blurs the rest of the targets in Graph
   * if present.
   *
   * @private
   * @param {Array} graphTargets - List of all the items in the Graph
   * @param {d3.selection} canvasSVG - d3 selection node of canvas svg
   * @returns {Function} - returns callback function that handles hover action on legend item
   */
  const hoverHandler = (graphTargets, canvasSVG) => (item, state) => {
    const additionalHoverHandler = (
      shownTargets,
      svg,
      currentKey,
      hoverState,
      k,
    ) => {
      svg
        .selectAll(`.${styles.point}[aria-describedby="${k}"]`)
        .classed(styles.blur, state === constants.HOVER_EVENT.MOUSE_ENTER);
    };
    legendHoverHandler(graphTargets, canvasSVG, item.key, state, [
      additionalHoverHandler,
    ]);
    // Highlight the scatter data points of the item hovered on
    canvasSVG
      .selectAll(`path[aria-describedby="${item.key}"]`)
      .classed(styles.highlight, state === constants.HOVER_EVENT.MOUSE_ENTER);
    canvasSVG
      .selectAll(`svg[aria-describedby="${item.key}"]`)
      .classed(styles.highlight, state === constants.HOVER_EVENT.MOUSE_ENTER);
  
    // Highlight region(s) of the item hovered on, only if the content is currently displayed
    regionLegendHoverHandler(graphTargets, canvasSVG, item.key, state);
  };
  /**
   * A callback that will be sent to Graph class so that when graph is
   * created the Graph API will execute this callback function and the legend
   * items are loaded.
   *
   * @private
   * @param {object} config - Graph config object derived from input JSON
   * @param {object} eventHandlers - Object containing click and hover event handlers for legend item
   * @param {object} dataTarget - Data points object
   * @param {object} legendSVG - d3 element that will be need to render the legend
   * items into.
   * @returns {undefined} - returns nothing
   */
  const prepareLegendItems = (config, eventHandlers, dataTarget, legendSVG) => {
    if (dataTarget.label && dataTarget.label.display && legendSVG) {
      loadLegendItem(legendSVG, dataTarget, config, eventHandlers);
    }
  };
  /**
   * CLear the graph data points and scatter data points currently rendered
   *
   * @private
   * @param {d3.selection} canvasSVG - d3 selection node of canvas svg
   * @param {object} dataTarget - Data points object
   * @returns {object} - d3 select object
   */
  const clear = (canvasSVG, dataTarget) => d3RemoveElement(canvasSVG, `g[aria-describedby="${dataTarget.key}"]`);
  
  export {
    toggleDataPointSelection,
    translatePoints,
    translateScatterGraph,
    draw,
    clickHandler,
    hoverHandler,
    transformPoint,
    prepareLegendItems,
    processDataPoints,
    getDataPointValues,
    drawDataPoints,
    clear,
  };
  