// @flow

type Props = {
  name: string,
  age: number
, ...};

type DefaultProps = { age: number, ... };

declare opaque type O1;
declare opaque type O2;

// $Keys<T> TODO this is not an EvalT
const countries = {
  US: "United States",
  IT: "Italy",
  FR: "France"
};

type Country = keyof typeof countries;
//   ^

type KeysPoly<K> = keyof K;
//   ^

// $Values<T>
type Prop$Values = Values<Props>;
//   ^

const frozenObject = Object.freeze({ A: "a", B: "b" });
type ValuesOfFrozenObject = Values<typeof frozenObject>;
//   ^

type ValuesPoly<X> = Values<X>;
//   ^

type ValuesPolyBound<X extends { f: number, ... }> = Values<X>;
//   ^

// Readonly<T>
type ReadOnlyObj = Readonly<{
//   ^
  key: any
, ...}>;

// $Exact<T>
// see exact.js

// Indexed Access
type ElementTypeProps = Props["name"];
//   ^
type ElementTypePropsPoly<K> = Props[K];
//   ^
type ElementTypePropsPolyBounded<K extends "name" | "age"> = Props[K];
//   ^
type ElementTypePropsPolyBoundedEmpty<K extends "phone"> = Props[K];
//   ^

// $NonMaybeType<T>
type NonMaybeTypeNumber = NonNullable<?number>;
//   ^
type NonMaybeTypeAbstract<X> = NonNullable<X>;
//   ^

// mapped types
type MappedTypeProps = {[K in keyof Props]: Array<Props[K]>}
//   ^

type MappedTypePoly<X, Y> = {[K in keyof { a: X, b?: Y, ... }]: Array<{ a: X, b?: Y, ... }[K]>};
//   ^

type FnObj = { getName: () => string, getAge: () => number, ... };
type MappedTypeFnReturnTypes = {[K in keyof FnObj]: { k: K, v: FnObj[K] extends () => infer V ? V : empty, ... } };
//   ^

// conditional types
type ExtractPropType = <T>({ prop: T, ... }) => T;
type PropObj = { prop: number, ... };
type ConditionalExtractPropType = PropObj extends { prop: infer T, ... } ? T : empty;
//   ^

type NestedObj = {
  readonly status: ?number,
  readonly data: ?ReadonlyArray<{
    readonly foo: ?{
      readonly bar: number
   }
 }>
};

// If you wanted to extract the type for `bar`, you could use conditional type:
type ConditionalNestedObjType = NestedObj extends {
//   ^
  readonly data: ?ReadonlyArray<{
    readonly foo: ?{
      readonly bar: ?infer T
, ...    }
, ...  }>
, ...} ? T : empty;

type ConditionalIdPoly<R> = R extends infer N ? N : empty;
//   ^

type PropObjPoly<P> = { prop: P, ... };
type ConditionalExtractPropTypePoly<P> = PropObjPoly<P> extends { prop: infer T, ... } ? T : empty;
//   ^

// $Exports<T>
type ExportsM = $Exports<"lib_m">;
//   ^

// Multi-params (ordering)
declare function right_order<T extends {...}, K extends T>(): T[K];
//               ^
declare function wrong_order<K extends T, T extends {...}>(): T[K];
//               ^

// Recursive
type RecursiveTypeDestructor = {
//   ^
  f: {
    g: RecursiveTypeDestructor['f']
 }
};

type RecursiveTypeDestructorPoly<X> = {
//   ^
  f: {h: RecursiveTypeDestructorPoly<X>['f']} | X // TODO
};

// Nested
type $Pick<O extends {...}, K extends keyof O> = NonNullable<O>[K];
//   ^

// ReadOnly+destructuring

function f({ x }: { x: ReadOnlyObj, ... }) {}
//           ^

// Non-evaluated due to alias
function non_evaluated() {
  type T = Readonly<{
    prop: R,
    ...
  }>;

  type R = Readonly<{
    ...T,
    type: 'blah',
    ...
  }>;

  declare const obj: R;

  const spread = {...obj};
//      ^
// Only show '{prop: R, type: "blah", ...}'. Do not expand R.
}


// TODO
// React.ElementConfigType
// React.ElementRefType
