/**
 * Removes keys from an object where the value is blank or not present
 * Blank values include: undefined, null, empty string '', and whitespace-only strings
 * Supports nested objects and arrays for deep cleaning
 * @param {any} obj - The object/value to clean
 * @param {Object} options - Configuration options
 * @param {boolean} [options.removeWhitespace=true] - Whether to treat whitespace-only strings as blank
 * @param {boolean} [options.deep=true] - Whether to recursively clean nested objects and arrays
 * @param {boolean} [options.removeEmptyObjects=false] - Whether to remove empty objects after cleaning
 * @param {boolean} [options.removeEmptyArrays=false] - Whether to remove empty arrays after cleaning
 * @returns {any} A new object/value with blank values removed
 */

/* eslint-disable @typescript-eslint/no-explicit-any */
interface RemoveBlankKeysOptions {
  removeWhitespace?: boolean;
  deep?: boolean;
  removeEmptyObjects?: boolean;
  removeEmptyArrays?: boolean;
}

const isBlank = (
  value: any,
  removeWhitespace: boolean,
): boolean =>
  value === undefined ||
  value === null ||
  value === '' ||
  (removeWhitespace && typeof value === 'string' && value.trim() === '');

const removeBlankKeys = <T>(
  obj: T,
  options: RemoveBlankKeysOptions = {},
): T => {
  const {
    removeWhitespace = true,
    deep = true,
    removeEmptyObjects = false,
    removeEmptyArrays = false,
  } = options;

  // Handle null/undefined primitives
  if (obj === null || obj === undefined) return obj;

  // Handle arrays
  if (Array.isArray(obj)) {
    if (!deep) return obj;

    const cleanedArray = obj
      .map((item) => removeBlankKeys(item, options))
      .filter((item) => !isBlank(item, removeWhitespace));

    // Remove empty arrays if option is set
    if (removeEmptyArrays && cleanedArray.length === 0) {
      return undefined as T;
    }

    return cleanedArray as T;
  }

  if (obj instanceof Date) {
    return obj;
  }

  if (typeof obj === 'object') {
    const result: Record<string, any> = {};

    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        const value = obj[key];

        // Skip blank values
        if (isBlank(value, removeWhitespace)) continue;

        // Recursively clean nested objects/arrays if deep is enabled
        if (deep && (Array.isArray(value) || (typeof value === 'object' && value !== null))) {
          const cleanedValue = removeBlankKeys(value, options);

          // Skip if the cleaned nested value became blank/empty
          if (isBlank(cleanedValue, removeWhitespace)) continue;
          if (removeEmptyObjects && cleanedValue !== null && typeof cleanedValue === 'object' && !Array.isArray(cleanedValue) && Object.keys(cleanedValue).length === 0) continue;
          if (removeEmptyArrays && Array.isArray(cleanedValue) && cleanedValue.length === 0) continue;

          result[key] = cleanedValue;
        } else {
          result[key] = value;
        }
      }
    }

    // Return undefined for empty objects if option is set
    if (removeEmptyObjects && Object.keys(result).length === 0) {
      return undefined as T;
    }

    return result as T;
  }

  // Return primitives as-is
  return obj;
};

export default removeBlankKeys;