/**
 * Implement an `AssignAll` generic that takes a tuple
 * containing object types and merges all of them.
 *
 * Bonus: make it *Tail-Recursive*.
 */
namespace assign {
 
  declare function assign<
    // Infer `Objects` as a tuple of objects:
    Objects extends [{}, ...{}[]]
  >(...objects: Objects): AssignAll<Objects>;
 
  type AssignAll<Tuple> = Tuple extends [infer Obj, ...infer Rest]
    ? Obj & AssignAll<Rest>
    : {}
 
  // Two objects
  const res1 = assign({ name: "Michel", age: 82 }, { childrenCount: 3 });
  type expected1 = { name: string; age: number, childrenCount: number };
  type test1 = Expect<Equal<typeof res1, expected1>>;
 
  // Three objects
  const res2 = assign(
    { protocol: "https" as const },
    { domain: "type-level-typescript.com" },
    { path: "/recursive-types" }
  );
  type expected2 = { protocol: "https", domain: string, path: string};
  type test2 = Expect<Equal<typeof res2, expected2>>;
 
  // Five objects
  const res3 = assign( { a: true }, { b: 2 }, { c: "4" }, { d: null }, { 2: 2n });
  type expected3 = { a: boolean; b: number; c: string; d: null; 2: bigint };
  type test3 = Expect<Equal<typeof res3, expected3>>;
 
  // One object
  const res4 = assign({ fileName: "hello-world", extension: "txt" });
  type expected4 = { fileName: string; extension: string };
  type test4 = Expect<Equal<typeof res4, expected4>>;
}
 
/**
 * Type the `all` function to take a list of promises and
 * to turn them into a single promise containing a list of values.
 */
namespace promiseAll {
  declare function all<
    // Infer `Promises` as a tuple of promises:
    Promises extends [Promise<any>, ...Promise<any>[]],
  >(promises: Promises): Promise<UnwrapAll<Promises>>;
 
  type UnwrapAll<Promises> = Promises extends [Promise<infer R>, ...infer Rest]
    ? [R, ...UnwrapAll<Rest>]
    : [];
 
  // Two promises
  const res1 = all([Promise.resolve(20), Promise.resolve("Hello" as const)]);
  type expected1 = Promise<[number, "Hello"]>;
  type test1 = Expect<Equal<typeof res1, expected1>>;
 
  // Three promises
  const res2 = all([
    Promise.resolve(true),
    Promise.resolve("!"),
    Promise.resolve({}),
  ]);
  type expected2 = Promise<[boolean, string, {}]>;
  type test2 = Expect<Equal<typeof res2, expected2>>;
 
  // Five promises
  const res3 = all([
    Promise.resolve(3),
    Promise.resolve("Hello" as const),
    Promise.resolve(true),
    Promise.resolve({ key: "value" }),
    Promise.resolve(["array"]),
  ]);
  type expected3 = Promise<
    [number, "Hello", boolean, { key: string }, string[]]
  >;
  type test3 = Expect<Equal<typeof res3, expected3>>;
}
 
/**
 * Type the `filterTable` function to take a Table,
 * a list of column names, and to return a table that
 * only contains columns with these names.
 */
namespace filterTable {
 
  type Column = { name: string, values: unknown[] }
  
  declare function filterTable<
    // Infer `T` as a tuple containing columns:
    T extends [Column, ...Column[]],
    // Infer `N` as a union of string literal type:
    N extends string
  >(table: T, columnNames: N[]): FilterTable<T, N>;
 
  type FilterTable<Table, NameUnion> = Table extends [infer A, ...infer B] 
    ? A extends {"name": NameUnion}
       ? [A, ...FilterTable<B, NameUnion>]
       : FilterTable<B, NameUnion>
    : []
 
  declare const userTable: [
    { name: 'firstName', values: string[] },
    { name: 'lastName', values: string[] },
    { name: 'age', values: number[] },
  ]
 
  const res1 = filterTable(userTable, ['age']);
  type test1 = Expect<Equal<typeof res1, [
    { name: 'age', values: number[] }
  ]>>;
 
  const res2 = filterTable(userTable, ['firstName', 'lastName']);
  type test2 = Expect<Equal<typeof res2, [
    { name: 'firstName', values: string[] },
    { name: 'lastName', values: string[] }
  ]>>;
 
  const res3 = filterTable(userTable, []);
  type test3 = Expect<Equal<typeof res3, []>>;
}
 
  1. 이거 벽느낌…
/**
 * Type lodash's `zip` function. 
 * `zip` takes several arrays containing different types of 
 * values, and turn them into a single array containing
 * tuples of values for each index.
 *
 * For example, `zip([1, 2], [true, false], ['a', 'b'])`
 * returns `[[1, true, 'a'], [2, false, 'b']]`.
 */
namespace zip {
  declare function zip<Arrays extends [any[], ...any[][]]>(...arrays: Arrays): UnwrapList<Arrays>[];
 
  type UnwrapList<T> = T extends [(infer A)[], ...infer B]
    ? [A, ...UnwrapList<B>]
    : []
 
  const res1 = zip([1, 2], [true, false]);
  // => [[1, true], [2, false]]
  type test1 = Expect<Equal<typeof res1, [number, boolean][]>>;
 
  const res2 = zip([1, 2], [true, false], ['a', 'b']);
  // => [[1, true, 'a'], [2, false, 'b']]
  type test2 = Expect<Equal<typeof res2, [number, boolean, string][]>>;
 
  const res3 = zip([1, 2, null], [true, false, undefined]);
  // => [[1, true], [2, false], [null, undefined]]
  type test3 = Expect<Equal<typeof res3, [number | null, boolean | undefined][]>>;
}
 
/**
 * Make a `Filter` generic that takes a tuple,
 * an arbitrary `Cond` type, filters out any element
 * that isn't assignable to `Cond`.
 */
namespace filter {
  type Filter<Tuple, Cond> = Tuple extends [infer A, ...infer B]
    ? A extends Cond
      ? [A, ...Filter<B, Cond>]
      : Filter<B, Cond>
    : []
 
  type res1 = Filter<[1, 2, "oops", 3, "hello"], number>;
  type test1 = Expect<Equal<res1, [1, 2, 3]>>;
 
  type res2 = Filter<["a", 1, "b", true, "c"], string>;
  type test2 = Expect<Equal<res2, ["a", "b", "c"]>>;
 
  type res3 = Filter<["hello", null, 42, {}, [], undefined], {}>;
  type test3 = Expect<Equal<res3, ["hello", 42, {}, []]>>;
  //                                 ^      ^
  // Note: strings and numbers are assignable to the `{}` object type.
}
 
  1. 이상하게 풀었네..
/**
 * Write a `WithIndex` type level function
 * that takes a tuple, and maps it to a tuple
 * of [value, index] pairs.
 *
 * Hint: you will need to use T["length"]
 * to generate indices
 */
namespace withIndex {
  type WithIndex<
    Tuple extends any[],
    Output extends any[] = []
  > = Tuple extends [...infer A, infer B]
    ? WithIndex<A, [[B, A["length"]], ...Output]>
    : Output
 
  type res1 = WithIndex<['a']>;
  type test1 = Expect<Equal<res1, [['a', 0]]>>;
 
  type res2 = WithIndex<['a', 'b']>;
  type test2 = Expect<Equal<res2, [['a', 0], ['b', 1]]>>;
 
  type res3 = WithIndex<['a', 'b', 'c']>;
  type test3 = Expect<Equal<res3, [['a', 0], ['b', 1], ['c', 2]]>>;
}
 
/**
 * Write a `Take` type level function
 * that takes a tuple, a `N` number and
 * returns the first `N` elements of this
 * tuple.
 *
 * Hint: you will need to use T["length"]
 * to read the length of a tuple `T`.
 */
namespace take {
  type Take<
    Tuple extends any[],
    N,
    Output extends any[] = []
  > = Tuple extends [infer A, ...infer B]
    ? Output["length"] extends N 
      ? Output
      : Take<B, N, [...Output, A]>
    : Output
 
  type res1 = Take<[1, 2, 3], 2>;
  type test1 = Expect<Equal<res1, [1, 2]>>;
 
  type res2 = Take<[1, 2, 3], 1>;
  type test2 = Expect<Equal<res2, [1]>>;
 
  type res3 = Take<[1, 2, 3], 0>;
  type test3 = Expect<Equal<res3, []>>;
 
  type res4 = Take<[1, 2], 5>;
  type test4 = Expect<Equal<res4, [1, 2]>>;
}