import { Build } from "./utils";
import { FakeAsyncHash, FakeHash } from "../fake-hash";

declare global {
  interface Generator<T = unknown, TReturn = any, TNext = unknown> {
    union(
      this: Generator<number, TReturn, TNext>,
      that: Generator<number, TReturn, TNext> | number[]
    ): Generator<number, TReturn, TNext>;
    union(
      this: Generator<string, TReturn, TNext>,
      that: Generator<string, TReturn, TNext> | string[]
    ): Generator<string, TReturn, TNext>;
    union(
      this: Generator<boolean, TReturn, TNext>,
      that: Generator<boolean, TReturn, TNext> | boolean[]
    ): Generator<boolean, TReturn, TNext>;
    union(
      that: Generator<T, TReturn, TNext> | T[],
      comparitor: (a: T, b: T) => boolean
    ): Generator<T, TReturn, TNext>;
  }

  interface AsyncGenerator<T = unknown, TReturn = any, TNext = unknown> {
    union(
      this: AsyncGenerator<number, TReturn, TNext>,
      that:
        | AsyncGenerator<number, TReturn, TNext>
        | Generator<number, TReturn, TNext>
        | number[]
    ): AsyncGenerator<number, TReturn, TNext>;
    union(
      this: AsyncGenerator<string, TReturn, TNext>,
      that:
        | AsyncGenerator<string, TReturn, TNext>
        | Generator<string, TReturn, TNext>
        | string[]
    ): AsyncGenerator<string, TReturn, TNext>;
    union(
      this: AsyncGenerator<boolean, TReturn, TNext>,
      that:
        | AsyncGenerator<boolean, TReturn, TNext>
        | Generator<boolean, TReturn, TNext>
        | boolean[]
    ): AsyncGenerator<boolean, TReturn, TNext>;
    union(
      that:
        | AsyncGenerator<T, TReturn, TNext>
        | Generator<T, TReturn, TNext>
        | T[],
      comparitor: (a: T, b: T) => boolean
    ): AsyncGenerator<T, TReturn, TNext>;
  }
}

Build(
  "union",
  function* (that, comparitor = (a: unknown, b: unknown) => a === b) {
    const hash = FakeHash(comparitor);
    for (const item of this) {
      if (!hash.contains(item)) {
        yield item;
        hash.add(item);
      }
    }

    for (const item of that) {
      if (!hash.contains(item)) {
        yield item;
        hash.add(item);
      }
    }
  },
  async function* (that, comparitor = (a: unknown, b: unknown) => a === b) {
    const hash = FakeAsyncHash(comparitor);
    for await (const item of this) {
      if (!(await hash.contains(item))) {
        yield item;
        hash.add(item);
      }
    }

    for await (const item of that) {
      if (!(await hash.contains(item))) {
        yield item;
        hash.add(item);
      }
    }
  }
);
