/* eslint-disable max-nested-callbacks */
import { lengthEquals, mapFirst } from 'vest-utils';

import { ctx } from '../../enforceContext';
import { transformResult } from '../../ruleResult';
import { RuleRunReturn } from '../../utils/RuleRunReturn';

/**
 * Validates that a value is an array and all elements match at least one of the provided rules.
 * Each array element must pass at least one of the validation rules.
 *
 * @template T - The element type of the array
 * @param value - The array to validate
 * @param rules - One or more RuleInstances that elements should match
 * @returns RuleRunReturn indicating success or failure
 *
 * @example
 * ```typescript
 * // Eager API - array of strings
 * enforce(['a', 'b', 'c'])
 *   .isArrayOf(enforce.isString()); // passes
 *
 * enforce([1, 2, 'three'])
 *   .isArrayOf(enforce.isString()); // fails
 *
 * // Lazy API - array of numbers or strings
 * const mixedArrayRule = enforce.isArrayOf(
 *   enforce.isNumber(),
 *   enforce.isString()
 * );
 *
 * mixedArrayRule.test([1, 'two', 3, 'four']); // true
 * mixedArrayRule.test([1, 2, true]); // false (boolean not allowed)
 *
 * // Complex schema validation
 * const usersRule = enforce.isArrayOf(
 *   enforce.shape({
 *     name: enforce.isString(),
 *     age: enforce.isNumber()
 *   })
 * );
 *
 * usersRule.test([
 *   { name: 'John', age: 30 },
 *   { name: 'Jane', age: 25 }
 * ]); // true
 * ```
 */

export function isArrayOf<T>(value: T[], ...rules: any[]): RuleRunReturn<T[]> {
  if (!Array.isArray(value)) {
    return RuleRunReturn.Failing(value);
  }

  const parsedArray: any[] = [];

  const failingResult = mapFirst(value, (item, breakout, index) => {
    const res = ctx.run({ value: item, set: true, meta: { index } }, () => {
      let lastRes: RuleRunReturn<any> | undefined;
      let passingTransformedType: any = item;

      // Try each rule with the item - any rule passing is OK
      const anyPass = rules.some(rule => {
        const rawResult = rule.run(item);
        lastRes = rawResult;
        const transformed = transformResult(rawResult, 'isArrayOf', item);
        if (transformed.pass) {
          passingTransformedType = rawResult.type ?? item;
        }
        return transformed.pass;
      });

      if (anyPass) {
        parsedArray.push(passingTransformedType);
        return RuleRunReturn.Passing(passingTransformedType);
      }

      // If failed and we have a single rule, return its failure (might contain nested path)
      if (lengthEquals(rules, 1) && lastRes) {
        return lastRes;
      }

      return RuleRunReturn.Failing(item);
    });

    if (!res.pass) {
      const currentPath = res.path || [];
      const newRes = { ...res, path: [index.toString(), ...currentPath] };
      breakout(true, newRes);
    }
  });

  return failingResult || RuleRunReturn.Passing(parsedArray as T[]);
}

// Type for isArrayOf rule instance - should chain array rules like isArray does
export type IsArrayOfRuleInstance<
  T,
  TInput = T,
> = import('../arrayRules').ArrayRuleInstance<T, TInput>;
