/**
 * Performs a deep comparison between two objects, identifying differences with smart equality rules.
 * This function is particularly useful for comparing API responses, form data changes, or object state.
 *
 * Smart equality features:
 * - Loose equality between strings and numbers ("123" == 123)
 * - Treats empty strings and null as equal ('' == null)
 * - Treats null and undefined as equal for practical purposes
 * - Can ignore specific keys during comparison
 * - Handles arrays with order-insensitive comparison
 *
 * @param obj1 - First object to compare
 * @param obj2 - Second object to compare
 * @param path - Current property path (used internally for recursion, default: '')
 * @param ignoreKeys - Array of keys to ignore during comparison (default: [])
 * @returns Array of difference objects, each containing path and differing values
 *
 * @example
 * // Basic object comparison
 * const diff = deepCompareObjectsSmart(
 *   { name: 'John', age: 30 },
 *   { name: 'Jane', age: 30 }
 * );
 * // Returns: [{ path: 'name', value1: 'John', value2: 'Jane' }]
 *
 * @example
 * // Smart string/number comparison
 * const diff = deepCompareObjectsSmart(
 *   { id: '123', active: true },
 *   { id: 123, active: true }
 * );
 * // Returns: [] (no differences - '123' == 123)
 *
 * @example
 * // Empty string vs null equality
 * const diff = deepCompareObjectsSmart(
 *   { description: '', tags: null },
 *   { description: null, tags: '' }
 * );
 * // Returns: [] (no differences - '' == null)
 *
 * @example
 * // Array comparison (order-insensitive)
 * const diff = deepCompareObjectsSmart(
 *   { items: ['apple', 'banana'] },
 *   { items: ['banana', 'apple'] }
 * );
 * // Returns: [] (no differences - arrays have same items regardless of order)
 *
 * @example
 * // With ignored keys
 * const diff = deepCompareObjectsSmart(
 *   { id: 1, name: 'John', updatedAt: '2023-01-01' },
 *   { id: 1, name: 'John', updatedAt: '2023-01-02' },
 *   '',
 *   ['updatedAt']
 * );
 * // Returns: [] (ignored updatedAt difference)
 */
export function deepCompareObjectsSmart(
  obj1: any,
  obj2: any,
  path: string = '',
  ignoreKeys: string[] = [],
): Array<{ path: string; value1: any; value2: any }> {
  // Array to store all identified differences
  const differences: Array<{ path: string; value1: any; value2: any }> = [];

  /**
   * Smart equality function with business logic rules
   * @param val1 - First value to compare
   * @param val2 - Second value to compare
   * @returns True if values are considered equal based on smart rules
   */
  const isEqual = (val1: any, val2: any): boolean => {
    // Loose equality between strings and numbers
    // e.g., "123" == 123, "0" == 0
    if (
      (typeof val1 === 'string' && typeof val2 === 'number') ||
      (typeof val1 === 'number' && typeof val2 === 'string')
    ) {
      return val1 == val2; // Use loose equality (==) for type conversion
    }

    // Business rule: treat empty string and null as equal
    // Useful for form data where empty fields might be null or ''
    if ((val1 === '' && val2 === null) || (val1 === null && val2 === '')) {
      return true;
    }

    // Business rule: treat null and undefined as equal
    // Often null and undefined represent the same "no value" state
    if (
      (val1 === null && val2 === undefined) ||
      (val1 === undefined && val2 === null)
    ) {
      return true;
    }

    // Default to strict equality for all other cases
    return val1 === val2;
  };

  // Early exit if values are smart-equal
  if (isEqual(obj1, obj2)) {
    return differences;
  }

  // Handle primitive values or null/undefined
  // If either value is not an object, they are different
  if (
    typeof obj1 !== 'object' ||
    obj1 === null ||
    typeof obj2 !== 'object' ||
    obj2 === null
  ) {
    differences.push({ path, value1: obj1, value2: obj2 });
    return differences;
  }

  // Handle array comparison (order-insensitive)
  if (Array.isArray(obj1) && Array.isArray(obj2)) {
    // Different lengths mean arrays are different
    if (obj1.length !== obj2.length) {
      differences.push({ path, value1: obj1, value2: obj2 });
    } else {
      // Order-insensitive comparison: check if all items in obj1 exist in obj2
      const unmatchedItems = [...obj2]; // Copy of obj2 to track matches

      for (const item1 of obj1) {
        // Find an item in obj2 that deeply equals item1
        const index = unmatchedItems.findIndex(
          (item2) =>
            deepCompareObjectsSmart(item1, item2, '', ignoreKeys).length === 0,
        );

        if (index >= 0) {
          // Remove matched item from unmatchedItems
          unmatchedItems.splice(index, 1);
        } else {
          // No matching item found - arrays are different
          differences.push({ path, value1: obj1, value2: obj2 });
          break;
        }
      }
    }
    return differences;
  }

  // Handle object comparison
  // Get union of all keys from both objects
  const keys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);

  for (const key of keys) {
    // Skip keys in ignore list
    if (ignoreKeys.includes(key)) continue;

    // Build property path for nested differences
    const newPath = path ? `${path}.${key}` : key;

    // Recursively compare nested properties
    differences.push(
      ...deepCompareObjectsSmart(obj1[key], obj2[key], newPath, ignoreKeys),
    );
  }

  return differences;
}

/**
 * Usage in real-world scenarios:
 *
 * 1. Form change detection:
 *    const formChanges = deepCompareObjectsSmart(originalData, currentData);
 *    if (formChanges.length > 0) {
 *      // Enable save button
 *    }
 *
 * 2. API response validation:
 *    const diff = deepCompareObjectsSmart(expectedResponse, actualResponse, '', ['timestamp']);
 *    if (diff.length === 0) {
 *      console.log('API response matches expectations');
 *    }
 *
 * 3. State change tracking:
 *    const stateDiff = deepCompareObjectsSmart(prevState, nextState);
 *    stateDiff.forEach(({ path, value1, value2 }) => {
 *      console.log(`Changed: ${path} from ${value1} to ${value2}`);
 *    });
 *
 * Performance considerations:
 * - Complexity: O(n) where n is total number of properties
 * - Recursive function - watch for very deep object structures
 * - Consider using iterative approach for extremely large objects
 */
