import _typeof from '@babel/runtime/helpers/typeof';
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
import isNil from '../node_modules/lodash-es/isNil.js';
import _delay from '../node_modules/lodash-es/delay.js';
import 'core-js/modules/es.symbol.js';
import 'core-js/modules/es.array.concat.js';
import 'core-js/modules/es.array.fill.js';
import 'core-js/modules/es.array.filter.js';
import 'core-js/modules/es.array.find.js';
import 'core-js/modules/es.array.includes.js';
import 'core-js/modules/es.array.iterator.js';
import 'core-js/modules/es.array.join.js';
import 'core-js/modules/es.array.map.js';
import 'core-js/modules/es.array.slice.js';
import 'core-js/modules/es.array.sort.js';
import 'core-js/modules/es.number.constructor.js';
import 'core-js/modules/es.number.is-integer.js';
import 'core-js/modules/es.object.assign.js';
import 'core-js/modules/es.object.get-own-property-descriptor.js';
import 'core-js/modules/es.object.get-own-property-descriptors.js';
import 'core-js/modules/es.object.keys.js';
import 'core-js/modules/es.object.to-string.js';
import 'core-js/modules/es.regexp.constructor.js';
import 'core-js/modules/es.regexp.exec.js';
import 'core-js/modules/es.regexp.to-string.js';
import 'core-js/modules/es.string.includes.js';
import 'core-js/modules/web.dom-collections.for-each.js';
import 'core-js/modules/web.dom-collections.iterator.js';
import { useEffect } from 'react';
import { sortStates } from '../node_modules/@carbon/react/es/components/DataTable/state/sortStates.js';
import fileDownload from 'js-file-download';
import warning from 'warning';
import { firstBy } from 'thenby';
import { GUTTER, DASHBOARD_BREAKPOINTS, CARD_DIMENSIONS, ROW_HEIGHT, DASHBOARD_COLUMNS } from '../constants/LayoutConstants.js';
import { FILTER_EMPTY_STRING, EMPTY_STRING_DISPLAY_VALUE } from '../constants/Filters.js';
import { reactAttributes, htmlAttributes, svgAttributes, eventHandlers } from '../constants/HTMLAttributes.js';
import dayjs from './dayjs.js';

function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }

/**
 * Helper function to generate a CSV from an array of table cell data
 * Retrieve the column headers, then match and join the cell values
 * with each header
 * @param {Array<string | number | boolean>} data from table cells
 * @return {string} generated csv
 */
var generateCsv = function generateCsv(data) {
  var allHeadersObj = Object.assign.apply(Object, [{}].concat(_toConsumableArray(data.map(function (row) {
    return row.values;
  }))));
  var columnHeaders = Object.keys(allHeadersObj);
  var headerRow = "".concat(columnHeaders.join(','), "\n");
  var lastColumnId = columnHeaders.slice(-1)[0];
  return data.reduce(function (previousCsv, row) {
    return columnHeaders.reduce(function (previousNestedCsv, headerId) {
      var _row$values$headerId;
      var separator = headerId === lastColumnId ? '\n' : ',';
      return "".concat(previousNestedCsv).concat((_row$values$headerId = row.values[headerId]) !== null && _row$values$headerId !== void 0 ? _row$values$headerId : '').concat(separator);
    }, previousCsv);
  }, headerRow);
};

/**
 * Helper function to support downloading data as CSV
 * Retrieve the column headers, then match and join the cell values
 * with each header. When CSV is fully joined, download the file
 *
 * @param {Array<string | number>} data from table cells
 * @param {string} title file name to be saved as
 */
var csvDownloadHandler = function csvDownloadHandler(data) {
  var title = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'export';
  var csv = generateCsv(data);
  var exportedFilename = "".concat(title, ".csv");
  fileDownload(csv, exportedFilename);
};
var tableTranslateWithId = function tableTranslateWithId(i18n, id, state) {
  var batchCancel = i18n.batchCancel,
    itemsSelected = i18n.itemsSelected,
    itemSelected = i18n.itemSelected;
  switch (id) {
    case 'carbon.table.batch.cancel':
      return batchCancel;
    case 'carbon.table.batch.items.selected':
      return typeof itemsSelected === 'function' ? itemsSelected(state.totalSelected) : "".concat(state.totalSelected, " ").concat(itemsSelected);
    case 'carbon.table.batch.item.selected':
      return typeof itemSelected === 'function' ? itemSelected(state.totalSelected) : "".concat(state.totalSelected, " ").concat(itemSelected);
    case 'carbon.table.toolbar.search.label':
      return i18n.searchLabel;
    case 'carbon.table.toolbar.search.placeholder':
      return i18n.searchPlaceholder;
    case 'carbon.table.header.icon.description':
      if (state.isSortHeader) {
        // When transitioning, we know that the sequence of states is as follows:
        // NONE -> ASC -> DESC -> NONE
        if (state.sortDirection === sortStates.NONE) {
          return i18n.filterAscending;
        }
        if (state.sortDirection === sortStates.ASC) {
          return i18n.filterDescending;
        }
        return i18n.filterNone;
      }
      return i18n.filterAscending;
    default:
      return '';
  }
};

/** This function assumes you're using carbon widgets */
function scrollErrorIntoView() {
  var focus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
  var invalidField = document.querySelector('[data-invalid="true"]');
  if (invalidField) {
    invalidField.scrollIntoView({
      behavior: 'smooth'
    });
    if (focus) {
      _delay(function () {
        return invalidField.focus();
      }, 250);
    }
    return true;
  }
  return false;
}
var handleEnterKeyDown = function handleEnterKeyDown(evt, callback) {
  if (evt.key === 'Enter') {
    callback(evt);
  }
};
var defaultFunction = function defaultFunction(name) {
  return function () {
    var _process;
    if (!((_process = process) !== null && _process !== void 0 && (_process = _process.env) !== null && _process !== void 0 && _process.JEST_WORKER_ID)) {
      // eslint-disable-next-line no-console
      console.info("".concat(name, " not implemented"));
    }
  };
};
var sortTableData = function sortTableData(columnId, isTimestampColumn) {
  return function (a, b) {
    if (isNil(a)) {
      return 1;
    }
    if (isNil(b)) {
      return -1;
    }
    if (isTimestampColumn) {
      // support the sort if we have column with timestamp
      var dateA = dayjs(a);
      var dateB = dayjs(b);
      if (dateA < dateB) {
        return -1;
      }
      if (dateA > dateB) {
        return 1;
      }
    }
    if (typeof a === 'string' && !Number(a)) {
      return a.localeCompare(b);
    }
    if (Number(a) < Number(b)) {
      return -1;
    }
    if (Number(a) > Number(b)) {
      return 1;
    }
    return 0;
  };
};
var getSortedData = function getSortedData(inputData, columnId, direction, isTimestampColumn) {
  // clone inputData because sort mutates the array
  var sortedData = inputData.map(function (i) {
    return i;
  });
  return sortedData.sort(firstBy(function (row) {
    return row.values[columnId];
  }, {
    cmp: sortTableData(columnId, isTimestampColumn),
    direction: direction === 'ASC' ? 'asc' : 'desc'
  }));
};

/**
 * A simple helper function that stops prop on an event before calling back the callback function
 * @param {*} evt  event to stop
 * @param {*} callback  callback to call
 * @param  {...any} args
 */
var stopPropagationAndCallback = function stopPropagationAndCallback(evt, callback) {
  evt.stopPropagation();
  for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
    args[_key - 2] = arguments[_key];
  }
  callback.apply(void 0, args);
};

/**
 * Determines the smallest possible height that the dashboard can be while fitting all cards
 * Cards have a max height of 16 units
 * @param {array} cards list of cards
 */
var getGridHeight = function getGridHeight(cards) {
  return cards.length * 16;
};

/**
 * Checks to see if the bounds of a card (given x/y coordinates and width/height) fit the grid
 * @param {*} x  the current x location of a card
 * @param {*} y  the current y location of a card
 * @param {*} w  current width of a card
 * @param {*} h  current height of a card
 * @param {*} grid nested array of rows and columns with the current card index that is occupying them. for example, if the entire top row was taken by the first card it would look like this [[1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
 */
var canFit = function canFit(x, y, w, h, grid) {
  for (var i = x; i < x + w; i += 1) {
    for (var j = y; j < y + h; j += 1) {
      if (grid.length === i) return false;
      if (grid[i].length === j) return false;
      if (grid[i][j] !== 0) return false;
    }
  }
  return true;
};

/**
 * Generates a non overlapping layout given the cards and column/dimension configuration for a given layout
 * @param {*} layoutName
 * @param {*} cards
 * @param {*} dashboardColumns array of column counts for the different breakpoints (see DASHBOARD_COLUMNS)
 * @param {*} cardDimensions double object of card height and width keyed by card size and layout (see CARD_DIMENSIONS)
 * returns
 */
var getLayout = function getLayout(layoutName, cards, dashboardColumns, cardDimensions, existingLayout) {
  var currX = 0;
  var currY = 0;

  // This grid is used to determine where a card can fit in the layout
  var grid = Array(dashboardColumns[layoutName]).fill(0).map(function () {
    return Array(getGridHeight(cards)).fill(0);
  });

  // This function updates the grid to reflect a card
  var placeCard = function placeCard(x, y, w, h, num) {
    for (var i = x; i < x + w; i += 1) {
      for (var j = y; j < y + h; j += 1) {
        grid[i][j] = num;
      }
    }
  };
  var layout = cards.map(function (card, index) {
    var _cardDimensions$card$ = cardDimensions[card.size][layoutName],
      w = _cardDimensions$card$.w,
      h = _cardDimensions$card$.h; // These are width and height based on card.size.

    // Handle pre-existing cards
    var existingCardLayout = existingLayout === null || existingLayout === void 0 ? void 0 : existingLayout.find(function (_ref) {
      var i = _ref.i;
      return i === card.id;
    });
    if (existingCardLayout) {
      var x = existingCardLayout.x,
        y = existingCardLayout.y; // coordinates of a card that already exists
      // Need to 'try' here because we will get an error if the user passes a layout that doesn't work
      try {
        // Need to place each existing card into the grid so that new cards know their bounds
        placeCard(x, y, w, h, index + 1);
      } catch (err) {
        // In this case, since we didn't explicitly place it, react-grid-layout will handle it for us
        // eslint-disable-next-line no-console
        console.error('Error displaying user defined layout: ', err);
      }
      return _objectSpread(_objectSpread({}, existingCardLayout), {}, {
        w: w,
        h: h
      });
    }

    // Handle new cards
    while (!canFit(currX, currY, w, h, grid)) {
      // checks each position of the grid to see if the card fits, updating the currX and currY along the way
      currX += 1;
      if (currX > dashboardColumns[layoutName]) {
        currX = 0;
        currY += 1;
        if (currY > getGridHeight(cards)) {
          return null;
        }
      }
    }
    // Need to 'try' here because we will get an error if the user passes a layout that doesn't work
    try {
      placeCard(currX, currY, w, h, index + 1);
    } catch (err) {
      // eslint-disable-next-line no-console
      console.error('Error displaying user defined layout: ', err);
    }
    var cardLayout = {
      i: card.id,
      x: currX,
      y: currY,
      w: w,
      h: h
    };
    currX += w;
    return cardLayout;
  }).filter(function (i) {
    return i !== null;
  });
  return layout;
};

/**
 * Returns { x: width in pixels, y: height in pixels }
 * This is used to set min-width and min-height of the card based on the current breakpoint
 */
var getCardMinSize = function getCardMinSize(breakpoint, size) {
  var dashboardBreakpoints = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DASHBOARD_BREAKPOINTS;
  var cardDimensions = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : CARD_DIMENSIONS;
  var rowHeight = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : ROW_HEIGHT;
  var dashboardColumns = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : DASHBOARD_COLUMNS;
  var totalCol = dashboardColumns[breakpoint];
  var columnWidth = (dashboardBreakpoints[breakpoint] - (totalCol - 1) * GUTTER) / totalCol;
  var cardColumns = cardDimensions[size][breakpoint].w;
  var cardRows = cardDimensions[size][breakpoint].h;
  var cardSize = {
    x: cardColumns * columnWidth + (cardColumns - 1) * GUTTER,
    y: cardRows * rowHeight[breakpoint] + (cardRows - 1) * GUTTER
  };
  return cardSize;
};

/**
 * Checks if function argument is an empty string
 * @param {string} str argument to be checked
 * @returns {Boolean} is empty string or not
 */
var isEmptyString = function isEmptyString(str) {
  return typeof str === 'string' && str.length === 0;
};

/**
 * Searches through an array of keys for a searchTerm match
 * @param {Array<string>} keys to be searched
 * @param {string} searchTerm
 * @returns {Boolean} found or not
 */
var caseInsensitiveSearch = function caseInsensitiveSearch(keys, searchTerm) {
  return keys.some(function (key) {
    return key.toLowerCase().includes(searchTerm.toLowerCase());
  });
};
var data = '[Dd][Aa][Tt][Aa]';
var aria = '[Aa][Rr][Ii][Aa]';
var attributes = [].concat(_toConsumableArray(reactAttributes), _toConsumableArray(htmlAttributes), _toConsumableArray(svgAttributes), _toConsumableArray(eventHandlers)).join('|');
var validAttributes = RegExp("^((".concat(attributes, ")|((").concat(data, "|").concat(aria, "|x)-.*))$"));
/**
 * Filter out props that are not valid HTML or react library props like 'ref'.
 * See HTMLAttributes.js for more info
 * @param {object} props the props (attributes) to filter
 */
var filterValidAttributes = function filterValidAttributes(props) {
  return Object.keys(props).filter(function (prop) {
    return validAttributes.test(prop);
  }).reduce(function (filteredProps, propName) {
    // eslint-disable-next-line no-param-reassign
    filteredProps[propName] = props[propName];
    return filteredProps;
  }, {});
};

/**
 * Detect browser support for a given API
 * @param {Array<string>} api the API to be tested
 * @returns {Boolean} return true if browser has support
 */
var browserSupports = function browserSupports(api) {
  switch (api) {
    case 'ResizeObserver':
      return typeof ResizeObserver !== 'undefined';
    case 'IntersectionObserver':
      return typeof IntersectionObserver !== 'undefined';
    default:
      // There is no assigned value by default, so return undefined
      return undefined;
  }
};

/**
 * Helper function for using the overrides props as object or a function that returns an object
 * @param {Object | Function} props the props that should override existing props
 * @param {Object} originalProps the original props, can be used as input for creating new props
 */
var getOverrides = function getOverrides(props, originalProps) {
  return typeof props === 'function' ? props(originalProps) : props;
};

/**
 * Converts strings to DOM Elements, individually returned within a <body> node.
 * @param {Array} strings
 * @returns {Array} DOM Elements
 */
var convertStringsToDOMElement = function convertStringsToDOMElement() {
  var strings = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  var domparser = new DOMParser();
  var mimetype = 'text/html';
  return strings.map(function (string) {
    return domparser.parseFromString(string, mimetype).querySelector('body');
  });
};

/**
 * Converts a color in hexadecimal to the RGB values.
 * @param {string} hexColor
 * @returns {object} object with values for r, g, b properties
 */
var hexToRgb = function hexToRgb(hexColor) {
  var _rgbColors;
  var regexResult = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor);
  var radix = 16;
  var rgbColors;
  try {
    var r = parseInt(regexResult[1], radix);
    var g = parseInt(regexResult[2], radix);
    var b = parseInt(regexResult[3], radix);
    rgbColors = {
      r: r,
      g: g,
      b: b
    };
  } catch (_unused) {
    {
      warning(false, 'Incorrect color value used, expected hexadecimal string. Defaulting to gray.') ;
    }
  }
  return (_rgbColors = rgbColors) !== null && _rgbColors !== void 0 ? _rgbColors : {
    r: 200,
    g: 200,
    b: 200
  };
};

/**
 * If min and max are provided, this function checks to see if the integer value is within the range
 * @param {Number} value integer value
 * @param {Number} min optional
 * @param {Number} max optional
 */
var isNumberValidForMinMax = function isNumberValidForMinMax(value, min, max) {
  var valid = false;
  if (Number.isInteger(value)) {
    valid = true;
    if (min) {
      if (value < min) {
        valid = false;
      }
    }
    if (max) {
      if (value > max) {
        valid = false;
      }
    }
  }
  return valid;
};

/**
 * Given an array of keys and a callback, fire the callback only when the event.key matches one of the keys included in the array
 *
 * @param {string[]} keys An array of key names you want to match and fire the callback on
 * @param {func} callback A callback to be fired when the specific keys are pressed
 * @returns void
 */
var handleSpecificKeyDown = function handleSpecificKeyDown(keys, callback) {
  return function (evt) {
    if (keys.includes(evt.key)) {
      callback(evt);
    }
  };
};

/**
 * Returns filter id (includes special constant for empty string)
 * @param {string} selectedItem filter value
 * @returns {string}
 */
var getFilterValue = function getFilterValue(selectedItem) {
  if (selectedItem === null) {
    return '';
  }
  if (isEmptyString(selectedItem.id)) {
    return FILTER_EMPTY_STRING;
  }
  return selectedItem.id;
};

/**
 * Returns filter id for multiselect (includes special constant for empty string)
 * @param {string} selectedItem filter value
 * @returns {string}
 */
var getMultiselectFilterValue = function getMultiselectFilterValue(selectedItem) {
  if (isEmptyString(selectedItem.id)) {
    return FILTER_EMPTY_STRING;
  }
  return selectedItem.text;
};

/**
 * Returns filter text (includes check for empty string)
 * @param {Object} column table column containing applied filter
 * @param {*} filterValue
 * @returns {string} text to display in input field
 */
var getAppliedFilterText = function getAppliedFilterText(column, filterValue) {
  var filter = column.options.find(function (option) {
    if (filterValue === FILTER_EMPTY_STRING && option.id === '') {
      return true;
    }
    if (option.id === '') {
      return false;
    }
    return option.id === filterValue;
  });
  if (filter) {
    return filter.text;
  }
  return '';
};
var getSelectedItem = function getSelectedItem(value, emptyValue) {
  if (_typeof(value) === 'object') {
    return value;
  }
  if (value === FILTER_EMPTY_STRING) {
    return {
      id: '',
      text: emptyValue
    };
  }
  return {
    id: value,
    text: value
  };
};

/**
 * Returns array with selected filters (includes check for empty string)
 * @param {Object} column table column containing applied filter
 * @param {*} filterValue
 * @returns {Object[]} selected filters
 */
var getMultiSelectItems = function getMultiSelectItems(column, filterValue) {
  var _filterWithEmptyStrin;
  var filterWithEmptyString = column === null || column === void 0 ? void 0 : column.options.find(function (filter) {
    return isEmptyString(filter.id);
  });
  var displayEmptyValue = (_filterWithEmptyStrin = filterWithEmptyString === null || filterWithEmptyString === void 0 ? void 0 : filterWithEmptyString.text) !== null && _filterWithEmptyStrin !== void 0 ? _filterWithEmptyStrin : EMPTY_STRING_DISPLAY_VALUE;
  if (Array.isArray(filterValue)) {
    return filterValue.map(function (value) {
      return getSelectedItem(value, displayEmptyValue);
    });
  }
  if (filterValue === FILTER_EMPTY_STRING) {
    return [{
      id: '',
      text: displayEmptyValue
    }];
  }
  if (filterValue) {
    return [{
      id: filterValue,
      text: filterValue
    }];
  }
  return [];
};

/**
 * Hook to detect clicks outside of a specified element
 * @param {node} ref Upon interaction with this DOM node handler won't be invoked
 * @param {function} handler Callback for interaction event
 */
var useOnClickOutside = function useOnClickOutside(ref, handler) {
  useEffect(function () {
    var listener = function listener(event) {
      if (!ref.current || ref.current.contains(event.target)) {
        return;
      }
      handler(event);
    };
    document.addEventListener('mousedown', listener);
    document.addEventListener('touchstart', listener);
    return function () {
      document.removeEventListener('mousedown', listener);
      document.removeEventListener('touchstart', listener);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [ref.current, handler]);
};

export { browserSupports, canFit, caseInsensitiveSearch, convertStringsToDOMElement, csvDownloadHandler, defaultFunction, filterValidAttributes, generateCsv, getAppliedFilterText, getCardMinSize, getFilterValue, getGridHeight, getLayout, getMultiSelectItems, getMultiselectFilterValue, getOverrides, getSortedData, handleEnterKeyDown, handleSpecificKeyDown, hexToRgb, isEmptyString, isNumberValidForMinMax, scrollErrorIntoView, sortTableData, stopPropagationAndCallback, tableTranslateWithId, useOnClickOutside };
