import "./array";
import { Build, Promised } from "./utils";

declare global {
  interface Generator<T = unknown, TReturn = any, TNext = unknown> {
    /**
     * Correlates the elements of two sequences based on equality of keys
     * and groups the results. The default equality comparer is used to compare keys.
     * @param inner The sequence to join to the first sequence.
     * @param outer_key_selector A function to extract the join key from each element of the first sequence.
     * @param inner_key_selector A function to extract the join key from each element of the second sequence.
     * @param result_selector A function to create a result element from an element from the first sequence and a collection of matching elements from the second sequence.
     * @returns A generator that contains elements of type TResult that are obtained by performing a grouped join on two sequences.
     */
    group_join<TKey extends string | number | boolean, TInner, TResult>(
      inner: Generator<TInner> | TInner[],
      outer_key_selector: (item: T) => TKey,
      inner_key_selector: (item: TInner) => TKey,
      result_selector: (item: T, attached: Generator<TInner>) => TResult
    ): Generator<TResult>;
    /**
     * Correlates the elements of two sequences based on key equality and groups the results.
     * A specified comparitor is used to compare keys.
     * @param inner The sequence to join to the first sequence.
     * @param outer_key_selector A function to extract the join key from each element of the first sequence.
     * @param inner_key_selector A function to extract the join key from each element of the second sequence.
     * @param result_selector A function to create a result element from an element from the first sequence and a collection of matching elements from the second sequence.
     * @param comparitor A comparitor function to hash and compare keys.
     * @returns A generator that contains elements of type TResult that are obtained by performing a grouped join on two sequences.
     */
    group_join<TKey, TInner, TResult>(
      inner: Generator<TInner> | TInner[],
      outer_key_selector: (item: T) => TKey,
      inner_key_selector: (item: TInner) => TKey,
      result_selector: (item: T, attached: Generator<TInner>) => TResult,
      comparitor: (a: TKey, b: TKey) => boolean
    ): Generator<TResult>;
  }

  interface AsyncGenerator<T = unknown, TReturn = any, TNext = unknown> {
    /**
     * Correlates the elements of two sequences based on equality of keys
     * and groups the results. The default equality comparer is used to compare keys.
     * @param inner The sequence to join to the first sequence.
     * @param outer_key_selector A function to extract the join key from each element of the first sequence.
     * @param inner_key_selector A function to extract the join key from each element of the second sequence.
     * @param result_selector A function to create a result element from an element from the first sequence and a collection of matching elements from the second sequence.
     * @returns A generator that contains elements of type TResult that are obtained by performing a grouped join on two sequences.
     */
    group_join<TKey extends string | number | boolean, TInner, TResult>(
      inner: AsyncGenerator<TInner> | Generator<TInner> | TInner[],
      outer_key_selector: (item: T) => Promised<TKey>,
      inner_key_selector: (item: TInner) => Promised<TKey>,
      result_selector: (
        item: T,
        attached: AsyncGenerator<TInner>
      ) => Promised<TResult>
    ): AsyncGenerator<TResult>;
    /**
     * Correlates the elements of two sequences based on key equality and groups the results.
     * A specified comparitor is used to compare keys.
     * @param inner The sequence to join to the first sequence.
     * @param outer_key_selector A function to extract the join key from each element of the first sequence.
     * @param inner_key_selector A function to extract the join key from each element of the second sequence.
     * @param result_selector A function to create a result element from an element from the first sequence and a collection of matching elements from the second sequence.
     * @param comparitor A comparitor function to hash and compare keys.
     * @returns A generator that contains elements of type TResult that are obtained by performing a grouped join on two sequences.
     */
    group_join<TKey, TInner, TResult>(
      inner: AsyncGenerator<TInner> | Generator<TInner> | TInner[],
      outer_key_selector: (item: T) => Promised<TKey>,
      inner_key_selector: (item: TInner) => Promised<TKey>,
      result_selector: (
        item: T,
        attached: AsyncGenerator<TInner>
      ) => Promised<TResult>,
      comparitor: (a: TKey, b: TKey) => boolean
    ): AsyncGenerator<TResult>;
  }
}

Build(
  "group_join",
  function* (
    inner,
    outer_key_selector,
    inner_key_selector,
    result_selector,
    comparitor = (a, b) => a === b
  ) {
    const inner_array = Array.isArray(inner) ? inner : inner.array();
    for (const item of this) {
      const key = outer_key_selector(item);
      yield result_selector(
        item,
        (function* () {
          for (let i = 0; i < inner_array.length; i++) {
            const item = inner_array[i];
            const bkey = inner_key_selector(item);
            if (comparitor(key, bkey)) yield item;
          }
        })()
      );
    }
  },
  async function* (
    inner,
    outer_key_selector,
    inner_key_selector,
    result_selector,
    comparitor = (a, b) => a === b
  ) {
    const inner_array = Array.isArray(inner) ? inner : await inner.array();
    for await (const item of this) {
      const key = await Promise.resolve(outer_key_selector(item));
      yield await Promise.resolve(
        result_selector(
          item,
          (async function* () {
            for (let i = 0; i < inner_array.length; i++) {
              const item = inner_array[i];
              const bkey = await Promise.resolve(inner_key_selector(item));
              if (comparitor(key, bkey)) yield item;
            }
          })()
        )
      );
    }
  }
);
