import { Build, Promised } from "./utils";
import "./select";
import "./array";
import {
  Assert,
  IsFunction,
  IsNumber,
  IsString,
  IsTuple,
  IsUnion,
  PatternMatch,
} from "@paulpopat/safe-type";

declare global {
  interface Generator<T = unknown, TReturn = any, TNext = unknown> {
    order_by(
      this: Generator<number, TReturn, TNext>,
      descending?: "descending"
    ): Generator<T, TReturn, TNext>;
    order_by(
      this: Generator<string, TReturn, TNext>,
      descending?: "descending"
    ): Generator<T, TReturn, TNext>;
    order_by(
      key_selector: (item: T) => number,
      descending?: "descending"
    ): Generator<T, TReturn, TNext>;
    order_by(
      key_selector: (item: T) => string,
      descending?: "descending"
    ): Generator<T, TReturn, TNext>;
    order_by<TKey>(
      key_selector: (item: T) => TKey,
      comparitor: (a: TKey, b: TKey) => boolean,
      descending?: "descending"
    ): Generator<T, TReturn, TNext>;
  }

  interface AsyncGenerator<T = unknown, TReturn = any, TNext = unknown> {
    order_by(
      this: AsyncGenerator<number, TReturn, TNext>,
      descending?: "descending"
    ): AsyncGenerator<T, TReturn, TNext>;
    order_by(
      this: AsyncGenerator<string, TReturn, TNext>,
      descending?: "descending"
    ): AsyncGenerator<T, TReturn, TNext>;
    order_by(
      key_selector: (item: T) => Promised<number>,
      descending?: "descending"
    ): AsyncGenerator<T, TReturn, TNext>;
    order_by(
      key_selector: (item: T) => Promised<string>,
      descending?: "descending"
    ): AsyncGenerator<T, TReturn, TNext>;
    order_by<TKey>(
      key_selector: (item: T) => Promised<TKey>,
      comparitor: (a: TKey, b: TKey) => boolean,
      descending?: "descending"
    ): AsyncGenerator<T, TReturn, TNext>;
  }
}

function BuildArguments(args: IArguments): {
  key_selector: <T>(i: T) => T;
  comparitor: (a: unknown, b: unknown) => boolean;
  descending: boolean;
} {
  const default_key_selector = <T>(i: T) => i;
  const default_comparitor = (a: unknown, b: unknown) => {
    Assert(IsUnion(IsString, IsNumber), a);
    Assert(IsUnion(IsString, IsNumber), b);
    return a > b;
  };

  if (args.length === 0) {
    return {
      key_selector: default_key_selector,
      comparitor: default_comparitor,
      descending: false,
    };
  }

  return PatternMatch(
    IsTuple(IsString),
    IsTuple(IsFunction, IsString),
    IsTuple(IsFunction, IsFunction, IsString),
    IsTuple(IsFunction),
    IsTuple(IsFunction, IsFunction)
  )(
    ([descending]) => ({
      key_selector: default_key_selector,
      comparitor: default_comparitor,
      descending: descending != null,
    }),
    ([key_selector, descending]) => ({
      key_selector,
      comparitor: default_comparitor,
      descending: descending != null,
    }),
    ([key_selector, comparitor, descending]) => ({
      key_selector,
      comparitor,
      descending: descending != null,
    }),
    ([key_selector]) => ({
      key_selector,
      comparitor: default_comparitor,
      descending: false,
    }),
    ([key_selector, comparitor]) => ({
      key_selector,
      comparitor,
      descending: false,
    })
  )([...args] as any) as any;
}

Build(
  "order_by",
  function* () {
    const m = BuildArguments(arguments);
    let new_subject = this.select((i) => [m.key_selector(i), i] as const)
      .array()
      .sort(([a], [b]) => (m.comparitor(a, b) ? 1 : -1));
    if (m.descending) {
      new_subject = new_subject.reverse();
    }
    for (const item of new_subject) {
      yield item[1];
    }
  },
  async function* () {
    const m = BuildArguments(arguments);
    let new_subject = (
      await this.select(
        async (i) => [await m.key_selector(i), i] as const
      ).array()
    ).sort(([a], [b]) => (m.comparitor(a, b) ? 1 : -1));
    if (m.descending) {
      new_subject = new_subject.reverse();
    }
    for (const item of new_subject) {
      yield item[1];
    }
  }
);
