type Person = {
  name: string;
  age: number;
};

const person: Person = {
  name: 'Alice',
  age: 28,
};

type Optional<T> = {
  [K in keyof T]?: T[K];
};

type PartialPerson = Optional<Person>;

const p: PartialPerson = {
  name: 'Bob',
};

type Readonly<T> = { readonly [K in keyof T]: T[K] };
type ReadonlyPerson = Readonly<Person>;

const p2: ReadonlyPerson = {
  name: 'Hans',
  age: 32,
};

type User = {
  name: string;
  settings: {
    darkMode: boolean;
  };
};

type ReadonlyUser = Readonly<User>;

const user: ReadonlyUser = {
  name: 'Fritz',
  settings: {
    darkMode: true,
  },
};

user.settings.darkMode = false;

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
type DeepReadonlyUser = DeepReadonly<User>;
const user2: DeepReadonlyUser = {
  name: 'Tim',
  settings: {
    darkMode: false,
  },
};

// user2.settings.darkMode = true;

type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};
type NullableUser = Nullable<User>;
type Required<T> = {
  [K in keyof T]-?: T[K];
};

type RequiredPerson = Required<PartialPerson>;
type Mutable<T> = {
  -readonly [K in keyof T]: T[K];
};

type MutablePerson = Mutable<ReadonlyUser>;

type x = Partial<{ user: string }>;

// Conditional Types
// T extends U ? X : Y

type IsString<T> = T extends string
  ? 'Ja, ist ein String'
  : 'Nein, kein String';

type Test1 = IsString<string>;
type Test2 = IsString<number>;

function checkType<T>(value: T): IsString<T> {
  return (
    typeof value === 'string' ? 'Ja, ist ein String' : 'Nein, kein String'
  ) as IsString<T>;
}

console.log(checkType('Hello'));
console.log(checkType(42));

type IsStringOrBoolean<T> = T extends string ? true : false;

function isString<T>(value: T): IsStringOrBoolean<T> {
  return (typeof value === 'string') as IsStringOrBoolean<T>;
}

console.log(isString('Test'));
console.log(isString(true));

function isString2(value: unknown): value is string {
  return typeof value === 'string';
}

let x: unknown;
x = 'test';
x = 42;

if (isString2(x)) {
  console.log('hey is string', x.length);
}

console.log(isString2({ obj: true }));
console.log(isString2('test'));

// type NonNullable<T> = T extends null | undefined ? never : T;
type A = NonNullable<string | null>;

type NullableTest = {
  test: string | null;
  deep: {
    test2: number | null;
  };
};

type B<T> = {
  [K in keyof T]: T[K] extends object ? B<T[K]> : NonNullable<T[K]>;
};

type NonNullableTest = B<NullableTest>;
const nnt: NonNullableTest = {
  test: 'string',
  deep: {
    test2: 42,
  },
};

type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type A2 = UnwrapPromise<Promise<string>>;
type B2 = UnwrapPromise<Promise<number>>;
type C2 = UnwrapPromise<boolean>;

type FirstElement<T> = T extends [any, ...any[]] ? T[0] : never;

type A3 = FirstElement<[1, 2, 3]>;
type B3 = FirstElement<['a', 2, true]>;
type C3 = FirstElement<[]>;

type xxx = Partial<A3>;

type Person2 = {
  name: string;
  age: number;
  address: string;
};

type PersonName = Pick<Person2, 'name'>;

type PersonWithoutAddress = Omit<Person2, 'address'>;
