import { Build } from "./utils";

declare global {
  interface Generator<T = unknown, TReturn = any, TNext = unknown> {
    /**
     * Produces the set difference of two sequences by using the default equality comparer to compare values.
     * @param that A sequence whose elements that also occur in the first sequence will cause those elements to be removed from the returned sequence.
     * @returns A sequence that contains the set difference of the elements of two sequences.
     */
    except(
      this: Generator<number, TReturn, TNext>,
      that: Generator<number, TReturn, TNext> | number[]
    ): Generator<number, TReturn, TNext>;
    /**
     * Produces the set difference of two sequences by using the default equality comparer to compare values.
     * @param that A sequence whose elements that also occur in the first sequence will cause those elements to be removed from the returned sequence.
     * @returns A sequence that contains the set difference of the elements of two sequences.
     */
    except(
      this: Generator<string, TReturn, TNext>,
      that: Generator<string, TReturn, TNext> | string[]
    ): Generator<string, TReturn, TNext>;
    /**
     * Produces the set difference of two sequences by using the default equality comparer to compare values.
     * @param that A sequence whose elements that also occur in the first sequence will cause those elements to be removed from the returned sequence.
     * @returns A sequence that contains the set difference of the elements of two sequences.
     */
    except(
      this: Generator<boolean, TReturn, TNext>,
      that: Generator<boolean, TReturn, TNext> | boolean[]
    ): Generator<boolean, TReturn, TNext>;
    /**
     * Produces the set difference of two sequences by using the default equality comparer to compare values.
     * @param that A sequence whose elements that also occur in the first sequence will cause those elements to be removed from the returned sequence.
     * @param comparitor A comparitor function to compare values.
     * @returns A sequence that contains the set difference of the elements of two sequences.
     */
    except(
      that: Generator<T, TReturn, TNext> | T[],
      comparitor: (a: T, b: T) => boolean
    ): Generator<T, TReturn, TNext>;
  }

  interface AsyncGenerator<T = unknown, TReturn = any, TNext = unknown> {
    /**
     * Produces the set difference of two sequences by using the default equality comparer to compare values.
     * @param that A sequence whose elements that also occur in the first sequence will cause those elements to be removed from the returned sequence.
     * @returns A sequence that contains the set difference of the elements of two sequences.
     */
    except(
      this: AsyncGenerator<number, TReturn, TNext>,
      that:
        | AsyncGenerator<number, TReturn, TNext>
        | Generator<number, TReturn, TNext>
        | number[]
    ): AsyncGenerator<number, TReturn, TNext>;
    /**
     * Produces the set difference of two sequences by using the default equality comparer to compare values.
     * @param that A sequence whose elements that also occur in the first sequence will cause those elements to be removed from the returned sequence.
     * @returns A sequence that contains the set difference of the elements of two sequences.
     */
    except(
      this: AsyncGenerator<string, TReturn, TNext>,
      that:
        | AsyncGenerator<string, TReturn, TNext>
        | Generator<string, TReturn, TNext>
        | string[]
    ): AsyncGenerator<string, TReturn, TNext>;
    /**
     * Produces the set difference of two sequences by using the default equality comparer to compare values.
     * @param that A sequence whose elements that also occur in the first sequence will cause those elements to be removed from the returned sequence.
     * @returns A sequence that contains the set difference of the elements of two sequences.
     */
    except(
      this: AsyncGenerator<boolean, TReturn, TNext>,
      that:
        | AsyncGenerator<boolean, TReturn, TNext>
        | Generator<boolean, TReturn, TNext>
        | boolean[]
    ): AsyncGenerator<boolean, TReturn, TNext>;
    /**
     * Produces the set difference of two sequences by using the default equality comparer to compare values.
     * @param that A sequence whose elements that also occur in the first sequence will cause those elements to be removed from the returned sequence.
     * @param comparitor A comparitor function to compare values.
     * @returns A sequence that contains the set difference of the elements of two sequences.
     */
    except(
      that:
        | AsyncGenerator<T, TReturn, TNext>
        | Generator<T, TReturn, TNext>
        | T[],
      comparitor: (a: T, b: T) => boolean
    ): AsyncGenerator<T, TReturn, TNext>;
  }
}

Build(
  "except",
  function* (that, comparitor = (a: unknown, b: unknown) => a === b) {
    const data = Array.isArray(that) ? that : that.array();
    for (const item of this) {
      let valid = true;
      for (const part of data) {
        if (comparitor(item, part)) {
          valid = false;
          break;
        }
      }

      if (valid) {
        yield item;
      }
    }
  },
  async function* (that, comparitor = (a: unknown, b: unknown) => a === b) {
    const data = Array.isArray(that) ? that : await that.array();
    const existing: unknown[] = [];
    for await (const item of this) {
      let valid = true;
      for (const part of data) {
        if (await Promise.resolve(comparitor(item, part))) {
          valid = false;
          break;
        }
      }

      if (valid) {
        existing.push(item);
        yield item;
      }
    }
  }
);
