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

declare global {
  interface Generator<T = unknown, TReturn = any, TNext = unknown> {
    /**
     * Applies an accumulator function over a sequence.
     * @param aggregator An accumulator function to be invoked on each element.
     * @returns The transformed final accumulator value.
     */
    aggregate(aggregator: (current: T, next: T) => T): T;
    /**
     * Applies an accumulator function over a sequence.
     * The specified seed value is used as the initial accumulator value.
     * @param acc The initial accumulator value.
     * @param aggregator An accumulator function to be invoked on each element.
     * @returns The transformed final accumulator value.
     */
    aggregate<TAcc>(
      acc: TAcc,
      aggregator: (current: TAcc, next: T) => TAcc
    ): TAcc;
    /**
     * Applies an accumulator function over a sequence.
     * The specified seed value is used as the initial accumulator value,
     * and the specified function is used to select the result value.
     * @param acc The initial accumulator value.
     * @param aggregator An accumulator function to be invoked on each element.
     * @param selector A function to transform the final accumulator value into the result value.
     * @returns The transformed final accumulator value.
     */
    aggregate<TAcc, TResult>(
      acc: TAcc,
      aggregator: (current: TAcc, next: T) => TAcc,
      selector: (part: TAcc) => TResult
    ): TResult;
  }

  interface AsyncGenerator<T = unknown, TReturn = any, TNext = unknown> {
    /**
     * Applies an accumulator function over a sequence.
     * @param aggregator An accumulator function to be invoked on each element.
     * @returns The transformed final accumulator value.
     */
    aggregate(aggregator: (current: T, next: T) => Promised<T>): Promise<T>;
    /**
     * Applies an accumulator function over a sequence.
     * The specified seed value is used as the initial accumulator value.
     * @param acc The initial accumulator value.
     * @param aggregator An accumulator function to be invoked on each element.
     * @returns The transformed final accumulator value.
     */
    aggregate<TAcc>(
      acc: TAcc,
      aggregator: (current: TAcc, next: T) => Promised<TAcc>
    ): Promise<TAcc>;
    /**
     * Applies an accumulator function over a sequence.
     * The specified seed value is used as the initial accumulator value,
     * and the specified function is used to select the result value.
     * @param acc The initial accumulator value.
     * @param aggregator An accumulator function to be invoked on each element.
     * @param selector A function to transform the final accumulator value into the result value.
     * @returns The transformed final accumulator value.
     */
    aggregate<TAcc, TResult>(
      acc: TAcc,
      aggregator: (current: TAcc, next: T) => Promised<TAcc>,
      selector: (part: TAcc) => Promised<TResult>
    ): Promise<TResult>;
  }
}

Build(
  "aggregate",
  function (acc, agg, sel) {
    if (!agg) {
      agg = acc as any;
      const s = this.next();
      if (s.done) {
        return undefined;
      }

      acc = s.value;
    }

    for (const item of this) {
      acc = agg(acc, item);
    }

    return sel ? sel(acc) : acc;
  },
  async function (acc, agg, sel) {
    if (!agg) {
      agg = acc as any;
      const s = await this.next();
      if (s.done) {
        return undefined;
      }

      acc = s.value;
    }

    for await (const item of this) {
      acc = await Promise.resolve(agg(acc, item));
    }

    return sel ? await Promise.resolve(sel(acc)) : acc;
  }
);
