'use strict';

const log = require('./global-slimmer-registry-7bbdb1f2.js');

/**
 * Device type enumeration for responsive design breakpoints.
 */
exports.DeviceType = void 0;
(function (DeviceType) {
  /**
   * Tablet device type.
   */
  DeviceType["TABLET"] = "tablet";
  /**
   * Mobile device type.
   */
  DeviceType["MOBILE"] = "mobile";
  /**
   * Desktop device type.
   */
  DeviceType["DESKTOP"] = "desktop";
})(exports.DeviceType || (exports.DeviceType = {}));

/* eslint-enable no-restricted-globals */
/**
 * find the parent dom with selector
 * if selector is a component tag name like 'vega-card',
 * we can call by findParent<HTMLVegaCardElement>(this.el, "vega-card") get the HTMLVegaCardElement;
 *
 * The function is generic, meaning that it can be used with any type of element
 *
 * @typedef HTMLElementTagNameMap
 * @typedef T
 * @param {HTMLElement} el - The element to start searching from.
 * @param {keyof HTMLElementTagNameMap | string} selector - The selector to find the parent element with.
 * @param {{ outerMost?: boolean }} [options] - options.outerMost: boolean - whether to find the outermost parent that matches the selector.
 * @param {boolean} [options.outerMost=false] - Whether to find the outermost parent that matches the selector.
 * @returns {Nullable<T>} The first element that matches the selector.
 */
function findParent(el, selector, options) {
  if (!el)
    return null;
  let current = el.parentElement || el.getRootNode()['host'];
  let found = null;
  while (current) {
    if (current.matches(selector)) {
      found = current;
      if (!(options === null || options === void 0 ? void 0 : options.outerMost)) {
        return found; // Return first (nearest) match by default
      }
      // Continue searching for outermost match
    }
    current = current.parentElement || current.getRootNode()['host'];
  }
  return found;
}
/**
 * It returns the device type based on the user agent
 *
 * @returns {DeviceType} The device type.
 */
const getDeviceType = () => {
  const userAgent = navigator.userAgent;
  if (/(tablet|ipad|playbook|silk|Macintosh)|(android(?!.*mobi))/i.test(userAgent) &&
    navigator.maxTouchPoints > 0) {
    return exports.DeviceType.TABLET;
  }
  else if (/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(userAgent)) {
    return exports.DeviceType.MOBILE;
  }
  return exports.DeviceType.DESKTOP;
};
/**
 * return class sting without '', null, undefined, Nan
 *
 * @param {string[]} classes - class array
 * @returns {string} - class string
 */
const arrayToClassString = (classes) => {
  if (classes) {
    return classes.filter((c) => c).join(' ');
  }
  else {
    return '';
  }
};
/**
 * If the element is the target, return true, otherwise, if the element has a parent, return the result
 * of calling isParent on the parent, otherwise return false.
 *
 * @param {HTMLElement} element - The element to check if it's a parent of the target.
 * @param {HTMLElement} target - The element that you want to check if it's a parent of the element.
 * @returns {boolean} A boolean value.
 */
function isParent(element, target) {
  if (!element || !target)
    return false;
  if (element === target)
    return true;
  return isParent(element.parentElement || element.getRootNode()['host'], target);
}
/**
 * "Scroll the container to the item."
 * The function takes two parameters:
 * container: The container to scroll.
 * item: The item to scroll to.
 * The function scrolls the container to the item
 *
 * @param {HTMLElement} container - The container element that you want to scroll.
 * @param {HTMLElement} item - The item to scroll to.
 * @returns {void} - return.
 */
function containerScrollToItem(container, item) {
  container.scrollTop = item.offsetTop;
}
/**
 * Check if the element is visible by checking if the element's height is greater than 0.
 *
 * @param {HTMLElement} element - HTMLElement - the element to check if it's visible
 * @returns {boolean} A boolean value.
 */
function isElementVisible(element) {
  return element.getBoundingClientRect().height > 0;
}
/**
 * It takes a string and returns a number, or null if the string is empty
 *
 * @param {string} input - The string to extract the number from.
 * @returns {number | null} A function that takes a string and returns a number or null.
 */
function extractNumberFromString(input) {
  if (input) {
    const result = input.replace(/\D/g, '');
    return result === '' ? null : Number(result);
  }
  return null;
}
/**
 * It takes a hexadecimal color string and returns an object with the red, green, and blue values
 *
 * @param {string} hex - The hexadecimal color value to convert to RGB.
 * @returns {RGBType | null} - The RGB color value
 */
function hexToRGB(hex) {
  hex = getHexFromShorthand(hex);
  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result
    ? {
      r: parseInt(result[1], 16),
      g: parseInt(result[2], 16),
      b: parseInt(result[3], 16),
    }
    : null;
}
/**
 * It convert RGB color to Hex color
 *
 * @param {string} rgb - The RGB color.
 * @returns {Nullable<string>} - The hex color
 */
function rgbToHex(rgb) {
  if (!rgb.includes('rgb'))
    return null;
  const rgbColors = rgb.match(/\d+/g);
  if (!rgbColors || rgbColors.length !== 3)
    return null;
  const hexColors = rgbColors.map((color) => {
    const hex = Number(color).toString(16);
    return hex.length === 1 ? '0' + hex : hex;
  });
  return `#${hexColors.join('')}`;
}
/**
 * It converts a hexadecimal color code from shorthand form to full form
 *
 * @param {string} hex - The hexadecimal color code that you want to convert to full form.
 * @returns {string} the hexFullForm variable.
 */
function getHexFromShorthand(hex) {
  if (!hex)
    hex = '';
  // Expand shorthand form (e.g. "03F") to full form (e.g. "#0033FF")
  const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  const hexFullForm = hex.replace(shorthandRegex, function (_m, r, g, b) {
    return '#' + r + r + g + g + b + b;
  });
  return hexFullForm;
}
/**
 * It returns true if the text in the element is wrapped.
 *
 * @param {HTMLElement} element - The element to be judged.
 * @returns {boolean} A boolean value.
 */
function isTextWrapped(element) {
  const lineHeight = parseFloat(getElementComputedStyle(element, 'line-height'));
  if (!isNaN(lineHeight)) {
    return element.clientHeight > lineHeight;
  }
  else {
    log.LogUtility.warn('The line height of this element is not obtained');
    return false;
  }
}
/**
 * "Get the computed style of an element."
 * The first parameter is the element whose style we want to get. The second parameter is the name of
 * the style we want to get
 *
 * @param {HTMLElement} element - The element whose style you want to get.
 * @param {string} styleName - The name of the style you want to get.
 * @returns {string} The computed style of the element.
 */
function getElementComputedStyle(element, styleName) {
  return window.getComputedStyle(element).getPropertyValue(styleName);
}
/**
 * Get the deep active element in shadow component
 *
 * @param {Document | ShadowRoot} root the root element
 * @param {boolean} fallbackToHost whether to fallback to host when active element is null, default is false.
 * In some cases, web component is used in a11y scenario, assistive technology may trigger click event on the host element but the active element is null, setting fallbackToHost to true can help to get the host element in this case.
 * @returns {Nullable<HTMLElement>} active element
 */
function getDeepActiveElementInShadow(
// eslint-disable-next-line no-restricted-globals
root = document, fallbackToHost = false) {
  const activeEl = root.activeElement;
  if (!activeEl && fallbackToHost && 'host' in root) {
    return root.host;
  }
  if (activeEl && activeEl.shadowRoot) {
    return getDeepActiveElementInShadow(activeEl.shadowRoot, fallbackToHost);
  }
  else {
    return activeEl;
  }
}
/**
 * Check if a node is an HTML element.
 *
 * @param {Node} element - The node to check.
 * @returns {element is HTMLElement} - True if the node is an HTML element, false otherwise.
 */
function isHTMLElement(element) {
  return element && element.nodeType === Node.ELEMENT_NODE;
}
/**
 * Checks if the click event is likely triggered by VoiceOver by comparing the click coordinates with the center of the clicked element.
 * VoiceOver clicks typically have coordinates that are very close to the center of the element,
 *   so if the click is within 1 pixel of the center, we consider it a VoiceOver click.
 *
 * @param {MouseEvent} clickEvent - The click event to check.
 * @returns {boolean} True if the click is likely a VoiceOver click, false otherwise.
 */
function checkIsVoiceOverClick(clickEvent) {
  if (clickEvent.detail === 0)
    return true;
  const targetElementRect = clickEvent.composedPath()[0].getBoundingClientRect();
  if (targetElementRect.width === 0 && targetElementRect.height === 0)
    return false;
  const targetElementMiddleX = Math.floor(targetElementRect.x + targetElementRect.width / 2);
  const targetElementMiddleY = Math.floor(targetElementRect.y + targetElementRect.height / 2);
  const clickPositionX = clickEvent.clientX;
  const clickPositionY = clickEvent.clientY;
  return (clickEvent.detail === 1 &&
    Math.abs(targetElementMiddleX - clickPositionX) <= 1 &&
    Math.abs(targetElementMiddleY - clickPositionY) <= 1);
}
const focusableSelector = [
  'a[href]',
  'area[href]',
  'button',
  'input',
  'select',
  'textarea',
  'iframe',
  'object',
  'embed',
  '[contenteditable="true"]',
  '[contenteditable=""]',
  '[tabindex]',
].join(',');
/**
 * Checks if the given element is focusable based on its tag, attributes, and tabIndex.
 *
 * @param {HTMLElement} element - The element to check.
 * @returns {boolean} True if the element is focusable, false otherwise.
 */
function isFocusableElement(element) {
  if (!element.matches(focusableSelector))
    return false;
  if (element.hasAttribute('disabled') || element.hasAttribute('inert'))
    return false;
  if (element.getAttribute('aria-hidden') === 'true')
    return false;
  if (element instanceof HTMLInputElement && element.type === 'hidden')
    return false;
  return element.tabIndex >= 0;
}
/**
 * Recursively searches for the first focusable element within the given container, including its shadow DOM descendants.
 *
 * @param {HTMLElement | ShadowRoot} container - The container to search within.
 * @returns {Nullable<HTMLElement>} The first focusable element found, or null if none exists.
 */
function getFirstFocusableElement(container) {
  /**
   * @param {ParentNode} node - The node to search within for a focusable element.
   * @returns {Nullable<HTMLElement>} The first focusable element found, or null.
   */
  const findFirst = (node) => {
    if (node instanceof HTMLElement && isFocusableElement(node)) {
      return node;
    }
    const elements = Array.from(node.querySelectorAll('*'));
    for (const element of elements) {
      if (isFocusableElement(element)) {
        return element;
      }
      if (element.shadowRoot) {
        const found = findFirst(element.shadowRoot);
        if (found)
          return found;
      }
    }
    return null;
  };
  if (container instanceof HTMLElement && container.shadowRoot) {
    if (isFocusableElement(container))
      return container;
    return findFirst(container) || findFirst(container.shadowRoot);
  }
  return findFirst(container);
}

exports.arrayToClassString = arrayToClassString;
exports.checkIsVoiceOverClick = checkIsVoiceOverClick;
exports.containerScrollToItem = containerScrollToItem;
exports.extractNumberFromString = extractNumberFromString;
exports.findParent = findParent;
exports.getDeepActiveElementInShadow = getDeepActiveElementInShadow;
exports.getDeviceType = getDeviceType;
exports.getElementComputedStyle = getElementComputedStyle;
exports.getFirstFocusableElement = getFirstFocusableElement;
exports.getHexFromShorthand = getHexFromShorthand;
exports.hexToRGB = hexToRGB;
exports.isElementVisible = isElementVisible;
exports.isHTMLElement = isHTMLElement;
exports.isParent = isParent;
exports.isTextWrapped = isTextWrapped;
exports.rgbToHex = rgbToHex;
