/**
 * @aedart/contracts
 * 
 * BSD-3-Clause, Copyright (c) 2023-present Alin Eugen Deac <aedart@gmail.com>.
 */

import { Throwable } from '@aedart/contracts/support/exceptions';

/**
 * Concat Spreadable
 *
 * Controls the behaviour of to treat this object's properties, when the object is concatenated via [`Array.concat()`]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat}
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/isConcatSpreadable
 */
interface ConcatSpreadable<T> extends ArrayLike<T> {
    /**
     * Determines how [`Array.concat()`]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat}
     * should treat this object as an array-like object and flattened to its array elements, or not.
     *
     * For array objects, the default behavior is to spread (flatten) elements (`[Symbol.isConcatSpreadable] === true`).
     *
     * For array-like objects, the default behavior is no spreading or flattening (`[Symbol.isConcatSpreadable] === false`).
     *
     * @type {boolean}
     */
    [Symbol.isConcatSpreadable]: boolean;
    /**
     * Length controls the number of properties to be added
     * when this object is concatenated via [`Array.concat()`]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat},
     * if [Symbol.isConcatSpreadable]{@link Symbol.isConcatSpreadable} is `true`.
     *
     * @type {number} Must be greater than or equal to 0
     */
    readonly length: number;
}

/**
 * Array Merge Exception
 *
 * To be thrown when unable to merge arrays.
 */
interface ArrayMergeException extends Throwable {
}

/**
 * Array Merge Callback
 */
type ArrayMergeCallback = (
/**
 * The current element being processed in the array
 *
 * @type {any}
 */
element: any, /* eslint-disable-line @typescript-eslint/no-explicit-any */ 
/**
 * The index of the current element being processed in the array.
 *
 * @type {number}
 */
index: number, 
/**
 * The concatenated array this callback was called upon
 *
 * @type {any[]}
 */
array: any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ 
/**
 * The merge options to be applied
 *
 * @type {Readonly<ArrayMergeOptions>}
 */
options: Readonly<ArrayMergeOptions>) => any;

/**
 * Array Merge Options
 */
interface ArrayMergeOptions {
    /**
     * Transfer functions
     *
     * **When `true`**: _functions are transferred into resulting array._
     *
     * **When `false` (_default behaviour_)**: _The merge operation will fail when a function
     * is encountered (functions are not cloneable by default)._
     *
     * @type {boolean}
     */
    transferFunctions?: boolean;
    /**
     * Merge callback to be applied
     *
     * **Note**: _When no callback is provided, then the merge function's default
     * callback is used._
     *
     * @type {ArrayMergeCallback}
     */
    callback?: ArrayMergeCallback;
}

/**
 * Array Merger
 *
 * Able to merge (deep merge) multiple source arrays into a single new array.
 */
interface ArrayMerger {
    /**
     * Use the following merge options or callback
     *
     * @param {ArrayMergeCallback | ArrayMergeOptions} [options]
     *
     * @return {this}
     *
     * @throws {ArrayMergeException}
     */
    using(options?: ArrayMergeCallback | ArrayMergeOptions): this;
    /**
     * Returns a merger of given source arrays
     *
     * @template SourceA extends any[]
     *
     * @param {SourceA} a
     *
     * @returns {SourceA}
     *
     * @throws {ArrayMergeException}
     */
    of<SourceA extends any[]>(a: SourceA): SourceA;
    /**
     * Returns a merger of given source arrays
     *
     * @template SourceA extends any[]
     * @template SourceB extends any[]
     *
     * @param {SourceA} a
     * @param {SourceB} b
     *
     * @returns {SourceA & SourceB}
     *
     * @throws {ArrayMergeException}
     */
    of<SourceA extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceB extends any[]>(a: SourceA, b: SourceB): SourceA & SourceB;
    /**
     * Returns a merger of given source arrays
     *
     * @template SourceA extends any[]
     * @template SourceB extends any[]
     * @template SourceC extends any[]
     *
     * @param {SourceA} a
     * @param {SourceB} b
     * @param {SourceC} c
     *
     * @returns {SourceA & SourceB & SourceC}
     *
     * @throws {ArrayMergeException}
     */
    of<SourceA extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceB extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceC extends any[]>(a: SourceA, b: SourceB, c: SourceC): SourceA & SourceB & SourceC;
    /**
     * Returns a merger of given source arrays
     *
     * @template SourceA extends any[]
     * @template SourceB extends any[]
     * @template SourceC extends any[]
     * @template SourceD extends any[]
     *
     * @param {SourceA} a
     * @param {SourceB} b
     * @param {SourceC} c
     * @param {SourceD} d
     *
     * @returns {SourceA & SourceB & SourceC & SourceD}
     *
     * @throws {ArrayMergeException}
     */
    of<SourceA extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceB extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceC extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceD extends any[]>(a: SourceA, b: SourceB, c: SourceC, d: SourceD): SourceA & SourceB & SourceC & SourceD;
    /**
     * Returns a merger of given source arrays
     *
     * @template SourceA extends any[]
     * @template SourceB extends any[]
     * @template SourceC extends any[]
     * @template SourceD extends any[]
     * @template SourceE extends any[]
     *
     * @param {SourceA} a
     * @param {SourceB} b
     * @param {SourceC} c
     * @param {SourceD} d
     * @param {SourceE} e
     *
     * @returns {SourceA & SourceB & SourceC & SourceD & SourceE}
     *
     * @throws {ArrayMergeException}
     */
    of<SourceA extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceB extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceC extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceD extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceE extends any[]>(a: SourceA, b: SourceB, c: SourceC, d: SourceD, e: SourceE): SourceA & SourceB & SourceC & SourceD & SourceE;
    /**
     * Returns a merger of given source arrays
     *
     * @template SourceA extends any[]
     * @template SourceB extends any[]
     * @template SourceC extends any[]
     * @template SourceD extends any[]
     * @template SourceE extends any[]
     * @template SourceF extends any[]
     *
     * @param {SourceA} a
     * @param {SourceB} b
     * @param {SourceC} c
     * @param {SourceD} d
     * @param {SourceE} e
     * @param {SourceF} f
     *
     * @returns {SourceA & SourceB & SourceC & SourceD & SourceE & SourceF}
     *
     * @throws {ArrayMergeException}
     */
    of<SourceA extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceB extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceC extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceD extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceE extends any[], /* eslint-disable-line @typescript-eslint/no-explicit-any */ SourceF extends any[]>(a: SourceA, b: SourceB, c: SourceC, d: SourceD, e: SourceE, f: SourceF): SourceA & SourceB & SourceC & SourceD & SourceE & SourceF;
    /**
     * Returns a merger of given source arrays
     *
     * @param {...any[]} sources
     *
     * @return {any[]}
     *
     * @throws {ArrayMergeException}
     */
    of(...sources: any[]): any[];
}

/**
 * Support Arrays identifier
 *
 * @type {Symbol}
 */
declare const SUPPORT_ARRAYS: unique symbol;

export { type ArrayMergeCallback, type ArrayMergeException, type ArrayMergeOptions, type ArrayMerger, type ConcatSpreadable, SUPPORT_ARRAYS };
