/**
 * Checks if the code is running in a web browser environment.
 * @returns {boolean} True if running in a web browser, false otherwise.
 */
export const isUsingWebBrowser = typeof window !== 'undefined';

/**
 * Generates a random integer between the specified minimum and maximum values, inclusive.
 * @param {number} min - The minimum value of the range.
 * @param {number} max - The maximum value of the range.
 */
export const getRandomInteger = (min: number, max: number): number => Math.floor(min + Math.random() * (max + 1 - min));

/**
 * Generates a random boolean value (true/false) with equal probability.
 */
export const getRandomBoolean = (): boolean => getRandomInteger(0, 1) === 0;

/**
 * Checks if the current device is a mobile device based on the user agent string.
 */
export const isMobileDevice = isUsingWebBrowser && /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);

/**
 * Checks if the application is running locally (on localhost).
 */
export const isRunningLocally = isUsingWebBrowser && location.hostname == 'localhost';

/**
 * Checks if the current device supports touch events.
 */
export const isTouchDevice = isUsingWebBrowser && (('ontouchstart' in window) || (navigator.maxTouchPoints && navigator.maxTouchPoints > 0));

/**
 * Formats a number into a currency string using the en-US locale.
 * @param {number} value - The number to be formatted as currency.
 * @example
 * ```typescript
 * const formattedCurrency = formatToCurrency(123456.78);
 * console.log(formattedCurrency); // Prints: $123,456.78
 * ```
 */
export const formatToCurrency = (value: number): string => Intl.NumberFormat('en-US').format(value);

/**
 * Pauses execution for the specified number of milliseconds asynchronously.
 * @param {number} ms - The number of milliseconds to wait.
 * @example
 * ```typescript
 * await wait(1000); // Waits for 1 second
 * ```
 */
export const wait = async (ms: number): Promise<void> => await new Promise<void>(r => setTimeout(r, ms));

/**
 * Calculates the distance between two points in one-dimensional space.
 * @param {number} p1 - The first point.
 * @param {number} p2 - The second point.
 * @example
 * ```typescript
 * const distance = calculateVectorDistance(-5, 5);
 * console.log(distance); // Prints: 10
 * ```
 */
export const calculateVectorDistance = (p1: number, p2: number): number => Math.sqrt(Math.pow(p1 - p2, 2));

/**
 * Calculates the Euclidean distance between two points in a 2D plane.
 * @param {number} x1 - The x-coordinate of the first point.
 * @param {number} y1 - The y-coordinate of the first point.
 * @param {number} x2 - The x-coordinate of the second point.
 * @param {number} y2 - The y-coordinate of the second point.
 * @example
 * ```typescript
 * const distance = calculateDistance(0, 0, 3, 4);
 * console.log(distance); // Prints: 5
 * ```
 */
export const calculateDistance = (x1: number, y1: number, x2: number, y2: number): number => Math.hypot(x2 - x1, y2 - y1);

/**
 * Calculates the percentage of a number.
 * @param {number} number - The number to calculate the percentage of.
 * @param {number} percent - The percentage to calculate.
 * @example:
 * ```typescript
 * const percentage = calculatePercentage(50, 20);
 * console.log(percentage); // Prints: 10
 * ```
 */
export const calculatePercentage = (number: number, percent: number): number => (percent / 100) * number;

/**
 * Calculates the percentage of a value relative to a maximum value.
 * @param {number} value - The value to calculate the percentage of.
 * @param {number} max - The maximum value.
 * @example
 * ```typescript
 * const relativePercentage = calculateRelativePercentage(25, 100);
 * console.log(relativePercentage); // Prints: 25
 * ```
 */
export const calculateRelativePercentage = (value: number, max: number): number => (100 * value) / max;

/**
 * Checks if a text contains right-to-left (RTL) characters.
 * ```typescript
 * const hasRTL = hasRTLCharacters('سلام');
 * console.log(hasRTL); // Prints: true
 * ```
 */
export const hasRTLCharacters = (text: string): boolean => /[\u0600-\u06FF]/.test(text);

/**
 * Decodes HTML encoded text.
 * @example
 * ```typescript
 * const decodedText = decodeHTMLEncodedText('&#9733; Hello &#9733;');
 * console.log(decodedText); // Prints: ★ Hello ★
 * ```
 */
export const decodeHTMLEncodedText = (text: string): string => text.replace(/&#(\d+);/g, (match, p1) => String.fromCodePoint(parseInt(p1)));

/**
 * Clamps a value within a specified range.
 * @param {number} min - The minimum value of the range.
 * @param {number} value - The value to be clamped.
 * @param {number} max - The maximum value of the range.
 * @example
 * ```typescript
 * const clampedValue = clamp(0, 10, 20);
 * console.log(clampedValue); // Prints: 10
 * ```
 */
export const clamp = (min: number, value: number, max: number): number => Math.min(Math.max(value, min), max);

/**
 * Converts RGB values to a hexadecimal color code.
 * @param {number} r - The red component (0-255).
 * @param {number} g - The green component (0-255).
 * @param {number} b - The blue component (0-255).
 * @example
 * ```typescript
 * const hexColor = convertRGBToHex(255, 0, 0);
 * console.log(hexColor); // Prints: #ff0000
 * ```
 */
export const convertRGBToHex = (r: number, g: number, b: number): string => "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);

/**
 * Converts a hexadecimal color code to its RGB representation.
 * @param {string} hex - The hexadecimal color code.
 * @example
 * ```typescript
 * const rgbArray = convertHexToRGB('#00ff00');
 * console.log(rgbArray); // Prints: [0, 255, 0]
 * ```
 */
export const convertHexToRGB = (hex: string): number[] | null => {
  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result ? result.slice(1).map(c => parseInt(c, 16)) : null;
}

/**
 * Converts a hexadecimal color code to its RGBA representation.
 * @param {string} hex - The hexadecimal color code.
 * @example
 * ```typescript
 * const rgbaArray = convertHexToRGBA('#ff0000');
 * console.log(rgbaArray); // Prints: [255, 0, 0, 1]
 * ```
 */
export const convertHexToRGBA = (hex: string): (number | string)[] => {
  const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i.exec(hex);
  return match ? match.slice(1, 5).map((c, i) => i < 3 ? parseInt(c, 16) : parseFloat((parseInt(c, 16) / 255).toFixed(2))) : [];
};


/**
 * Converts RGBA values to a hexadecimal color code with alpha.
 * @example
 * ```typescript
 * const hexWithAlpha = convertRGBAToHexWithAlpha(255, 0, 0, 0.5);
 * console.log(hexWithAlpha); // Prints: #ff000080
 * ```
 */
export function convertRGBAToHexWithAlpha(r: number, g: number, b: number, a: number): string {
  let _r = r.toString(16), _g = g.toString(16), _b = b.toString(16), _a = Math.round(a * 255).toString(16);
  if (_r.length == 1) _r = "0" + _r; if (_g.length == 1) _g = "0" + _g; if (_b.length == 1) _b = "0" + _b; if (_a.length == 1) _a = "0" + _a;
  return _r + _g + _b + _a;
}

/**
 * Linearly interpolates between two colors.
 * @param {number} a - The first color.
 * @param {number} b - The second color.
 * @param {number} amount - The interpolation amount (0-1).
 * @example
 * ```typescript
 * const interpolatedColor = interpolateColor(0xff0000, 0x00ff00, 0.5);
 * console.log(interpolatedColor); // Prints: #ffff00
 * ```
 */
export function interpolateColor(a: number, b: number, amount: number): string {
  const
    ar = a >> 16, ag = a >> 8 & 0xff, ab = a & 0xff,
    br = b >> 16, bg = b >> 8 & 0xff, bb = b & 0xff,
    rr = ar + amount * (br - ar),
    rg = ag + amount * (bg - ag),
    rb = ab + amount * (bb - ab);
  return ((1 << 24) + (rr << 16) + (rg << 8) + rb | 0).toString(16).slice(1);
}

/**
 * Sets the opacity of a hexadecimal color code.
 * @param {string} color - The hexadecimal color code.
 * @param {number} opacity - The opacity value (0-1).
 * @example
 * ```typescript
 * const modifiedColor = setHexOpacity('#ff0000', 0.5);
 * console.log(modifiedColor); // Prints: #ff000080
 * ```
 */
export const setHexOpacity = (color: string, opacity: number): string => {
  const rgba = convertHexToRGBA(color);
  const [r, g, b] = rgba.slice(0, 3).map(c => typeof c === 'number' ? Math.round(c * 255) : c);
  const alpha = opacity >= 0 && opacity <= 1 ? opacity : 1; // Ensure opacity is within valid range
  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
};

/**
 * Formats a number with appropriate suffixes (K, M, B, T) based on its magnitude.
 * 
 * @param {number} number - The number to be formatted.
 * @example
 * ```typescript
 * const formattedNumber = formatNumberWithSuffix(1234567);
 * console.log(formattedNumber); // Prints: 1.23M
 * ```
 */
export function formatNumberWithSuffix(number: number): string {
  const suffixes = ['', 'K', 'M', 'B', 'T'];
  let suffixIndex = 0;
  while (number >= 1e3 && suffixIndex < suffixes.length - 1) number /= 1e3 | suffixIndex++;
  return `${Number(number).toFixed(2).replace(/\.00$/, '')}${suffixes[suffixIndex]}`;
}

/**
 * Constructs a query string by appending key-value pairs from the given data object to the provided URL.
 * 
 * @param {string} url - The base URL to which the query string will be appended.
 * @param {object} data - The data object containing key-value pairs to be converted into query parameters.
 * @example
 * ```typescript
 * const url = dataToQuery('https://example.com/api', { page: 1, limit: 10 });
 * console.log(url); // Prints: https://example.com/api?page=1&limit=10
 * ```
 */
export const dataToQuery = (url: string, data: Record<string, string>): string =>
  url + '?' + Object.keys(data).map(key => key + '=' + data[key]).join('&');


/**
 * Generates a random GUID (Globally Unique Identifier).
 * 
 * @param {number} [length=10] - The length of the generated GUID. Default is 10.
 * @example
 * ```typescript
 * const generatedGUID = guid();
 * console.log(generatedGUID); // Prints: "a8b4e95d12"
 * ```
 */
export const guid = (length: number = 10): string => 'x'.repeat(length).replace(/x/g, () => (Math.random() * 16 | 0).toString(16));

/**
 * Adds a new script element to the document head with the provided script.
 * 
 * @param {string} script - The JavaScript script to be executed by the added script element.
 * @example
 * ```typescript
 * const script = "console.log('Hello, world!');";
 * addScriptByElement(script);
 * ```
 */
export function addScriptByElement(script: string): void {
  const scriptElement = document.createElement("script");
  scriptElement.innerHTML = script;
  document.head.appendChild(scriptElement);
}

/**
 * Checks if an element is visible in the viewport.
 * 
 * @param {Element} element - The element to check visibility for.
 * @param {boolean} [partiallyVisible=false] - Whether the element is considered visible if only partially visible. Default is false.
 */
export const isElementVisibleInViewport = (element: Element, partiallyVisible: boolean = false): boolean => {
  const { top, left, bottom, right } = element.getBoundingClientRect();
  const { innerHeight, innerWidth } = window;
  return partiallyVisible
    ? ((top > 0 && top < innerHeight) ||
      (bottom > 0 && bottom < innerHeight)) &&
    ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
    : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
};

/**
 * Measures the elapsed time taken by an asynchronous function to execute.
 * 
 * @param {Function} func - The asynchronous function to measure the elapsed time for.
 * @example
 * ```typescript
 * async function exampleAsyncFunction() {
 *     await wait(1000);
 *     console.log('Async function executed!');
 * }
 * const elapsedTime = await elapsed(exampleAsyncFunction);
 * console.log(elapsedTime); // Prints: Time taken in seconds
 * ```
 */
export async function elapsed(func: Function): Promise<number> {
  const start = Date.now();
  await func();
  return (Date.now() - start) / 1000;
}

