import {expectAssignable, expectNotAssignable, expectType} from 'tsd';
import type {Paths, UnknownArray} from '../index.d.ts';
import type {MapsSetsOrArrays, NonRecursiveType} from '../source/internal/type.d.ts';

declare const normal: Paths<{foo: string}>;
expectType<'foo'>(normal);

type DeepObject = {
	a: {
		b: {
			c: {
				d: string;
			};
		};
		b2: number[];
		b3: boolean;
	};
};
declare const deepObject: Paths<DeepObject>;
type DeepPath = 'a' | 'a.b' | 'a.b2' | 'a.b3' | 'a.b.c' | 'a.b.c.d' | `a.b2.${number}`;
expectType<DeepPath>(deepObject);

// Test for interface
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface InterfaceType extends DeepObject {}
declare const interfaceType: Paths<InterfaceType>;
expectType<DeepPath>(interfaceType);

declare const emptyObject: Paths<{}>;
expectType<never>(emptyObject);

declare const emptyArray: Paths<[]>;
expectType<never>(emptyArray);

declare const symbol: Paths<{[Symbol.iterator]: string}>;
expectType<never>(symbol);

declare const never: Paths<never>;
expectType<never>(never);

declare const date: Paths<{foo: Date}>;
expectType<'foo'>(date);

declare const mixed: Paths<{foo: boolean} | {bar: string}>;
expectType<'foo' | 'bar'>(mixed);

declare const array: Paths<Array<{foo: string}>>;
expectType<number | `${number}` | `${number}.foo`>(array);

declare const tuple: Paths<[{foo: string}]>;
expectType<'0' | '0.foo'>(tuple);

declare const deeplist: Paths<{foo: Array<{bar: boolean[]}>}>;
expectType<'foo' | `foo.${number}` | `foo.${number}.bar` | `foo.${number}.bar.${number}`>(deeplist);

declare const readonly: Paths<{foo: Readonly<{bar: string}>}>;
expectType<'foo' | 'foo.bar'>(readonly);

declare const readonlyArray: Paths<{foo: readonly string[]}>;
expectType<'foo' | `foo.${number}`>(readonlyArray);

declare const optional: Paths<{foo?: {bar?: number}}>;
expectType<'foo' | 'foo.bar'>(optional);

declare const record: Paths<Record<'a', any>>;
expectType<'a'>(record);

declare const record2: Paths<Record<1, unknown>>;
expectType<1 | '1'>(record2);

declare const map: Paths<{foo?: {bar?: Map<string, number>}}>;
expectType<'foo' | 'foo.bar'>(map);

declare const map2: Paths<Map<string, number>>;
expectType<never>(map2);

declare const readonlyMap: Paths<{foo?: {bar?: ReadonlyMap<string, number>}}>;
expectType<'foo' | 'foo.bar'>(readonlyMap);

declare const readonlyMap2: Paths<ReadonlyMap<string, number>>;
expectType<never>(readonlyMap2);

declare const weakMap: Paths<{foo?: {bar?: WeakMap<{a: string}, number>}}>;
expectType<'foo' | 'foo.bar'>(weakMap);

declare const weakMap2: Paths<WeakMap<{a: string}, number>>;
expectType<never>(weakMap2);

declare const set: Paths<{foo?: {bar?: Set<string>}}>;
expectType<'foo' | 'foo.bar'>(set);

declare const set2: Paths<Set<string>>;
expectType<never>(set2);

declare const readonlySet: Paths<{foo?: {bar?: ReadonlySet<string>}}>;
expectType<'foo' | 'foo.bar'>(readonlySet);

declare const readonlySet2: Paths<ReadonlySet<string>>;
expectType<never>(readonlySet2);

declare const weakSet: Paths<{foo?: {bar?: WeakSet<{a: string}>}}>;
expectType<'foo' | 'foo.bar'>(weakSet);

declare const weakSet2: Paths<WeakSet<{a: string}>>;
expectType<never>(weakSet2);

declare const nonRecursives: Paths<{a: NonRecursiveType | Exclude<MapsSetsOrArrays, UnknownArray>}>;
expectType<'a'>(nonRecursives);

// Test for unknown length array
declare const trailingSpreadTuple: Paths<[{a: string}, ...Array<{b: number}>]>;
expectType<number | `${number}` | '0.a' | `${number}.b`>(trailingSpreadTuple);

declare const trailingSpreadTuple1: Paths<[{a: string}, {b: number}, ...Array<{c: number}>]>;
expectType<number | `${number}` | '0.a' | '1.b' | `${number}.c`>(trailingSpreadTuple1);

declare const optionalElementsWithTrailingSpreadTuple: Paths<{foo: [{a: string}, ({b: number})?, ...Array<{c: number}>]}>;
expectType<'foo' | `foo.${number}` | 'foo.0.a' | 'foo.1.b' | `foo.${number}.c`>(optionalElementsWithTrailingSpreadTuple);

declare const optionalElementsWithTrailingSpreadTuple1: Paths<[({a: string})?, ({b: number})?, ...Array<{c: number}>]>;
expectType<number | `${number}` | '0.a' | '1.b' | `${number}.c`>(optionalElementsWithTrailingSpreadTuple1);

declare const leadingSpreadTuple: Paths<[...Array<{a: string}>, {b: number}]>;
expectType<number | `${number}` | `${number}.b` | `${number}.a`>(leadingSpreadTuple);

declare const leadingSpreadTuple1: Paths<[...Array<{a: string}>, {b: number}, {c: number}]>;
expectType<number | `${number}` | `${number}.b` | `${number}.c` | `${number}.a`>(leadingSpreadTuple1);

declare const middleSpreadTuple: Paths<[{a: string}, ...Array<{b: number}>, {c: boolean}]>;
expectType<number | `${number}` | '0.a' | `${number}.b` | `${number}.c`>(middleSpreadTuple);

// Circularly references
type MyEntity = {
	myOtherEntity?: MyOtherEntity;
};
type MyOtherEntity = {
	myEntity?: MyEntity;
};
type MyEntityPaths = Paths<MyEntity>;
expectAssignable<string>({} as MyEntityPaths);

// By default, the recursion limit should be reasonably long
type RecursiveFoo = {foo: RecursiveFoo};
expectAssignable<Paths<RecursiveFoo, {maxRecursionDepth: 10}>>('foo.foo.foo.foo.foo.foo.foo.foo');

declare const recursion0: Paths<RecursiveFoo, {maxRecursionDepth: 0}>;
expectType<'foo'>(recursion0);

declare const recursion1: Paths<RecursiveFoo, {maxRecursionDepth: 1}>;
expectType<'foo' | 'foo.foo'>(recursion1);

// Test a[0].b style
type Object1 = {
	arr: [{a: string}];
};
expectType<Paths<Object1, {bracketNotation: true}>>({} as 'arr' | 'arr[0]' | 'arr[0].a');

type Object2 = {
	arr: Array<{a: string}>;
	arr1: string[];
};
expectType<Paths<Object2, {bracketNotation: true}>>({} as 'arr' | 'arr1' | `arr[${number}]` | `arr[${number}].a` | `arr1[${number}]`);

type Object3 = {
	1: 'foo';
	'2': 'bar';
};
expectType<Paths<Object3, {bracketNotation: true}>>({} as '[1]' | '[2]');

type Object4 = {
	1: {
		a: string;
	};
};
expectType<Paths<Object4, {bracketNotation: true}>>({} as '[1]' | '[1].a');

type Object5 = {
	1: {
		2: string;
	};
};
expectType<Paths<Object5, {bracketNotation: true}>>({} as '[1]' | '[1][2]');

type deepArray = {
	arr: Array<Array<Array<{a: string}>>>;
};
expectType<Paths<deepArray, {bracketNotation: true}>>({} as 'arr' | `arr[${number}]` | `arr[${number}][${number}]` | `arr[${number}][${number}][${number}]` | `arr[${number}][${number}][${number}].a`);

type RecursionArray = RecursionArray[];
type RecursionArrayPaths = Paths<RecursionArray, {bracketNotation: true; maxRecursionDepth: 3}>;
expectAssignable<RecursionArrayPaths>({} as `[${number}][${number}][${number}][${number}]`);
expectNotAssignable<RecursionArrayPaths>({} as `[${number}][${number}][${number}][${number}][${number}]`);

// -- leavesOnly option --

declare const leaves: Paths<{a: {b: string; c?: Date; d: (x: string) => number; e: Map<string, string>; f?: {g?: Set<string>}}; h: boolean}, {leavesOnly: true}>;
expectType<'a.b' | 'a.c' | 'a.d' | 'a.e' | 'a.f.g' | 'h'>(leaves);

declare const unionLeaves: Paths<{a: {b?: number}} | {a: string; b?: {c: string}}, {leavesOnly: true}>;
expectType<'a.b' | 'a' | 'b.c'>(unionLeaves);

declare const unionLeaves1: Paths<{a: {} | {c: number}}, {leavesOnly: true}>;
expectType<'a' | 'a.c'>(unionLeaves1);

declare const unionLeaves2: Paths<{a: {[x: string]: number} | {c: number}}, {leavesOnly: true}>;
expectType<`a.${string}`>(unionLeaves2); // Collapsed union

declare const unionLeaves3: Paths<{a: string | {toLowerCase: number}}, {leavesOnly: true}>;
expectType<'a' | 'a.toLowerCase'>(unionLeaves3);

declare const emptyObjectLeaves: Paths<{a: {}}, {leavesOnly: true}>;
expectType<'a'>(emptyObjectLeaves);

declare const emptyArrayLeaves: Paths<{a: []}, {leavesOnly: true}>;
expectType<'a'>(emptyArrayLeaves);

declare const readonlyEmptyArrayLeaves: Paths<{a: readonly []}, {leavesOnly: true}>;
expectType<'a'>(readonlyEmptyArrayLeaves);

declare const arrayLeaves: Paths<string[], {leavesOnly: true}>;
expectType<number | `${number}`>(arrayLeaves);

declare const tupleLeaves: Paths<[string, number?], {leavesOnly: true}>;
expectType<'0' | '1'>(tupleLeaves);

declare const objectArrayLeaves: Paths<Array<{a: number}>, {leavesOnly: true}>;
expectType<`${number}.a`>(objectArrayLeaves);

declare const objectTupleLeaves: Paths<readonly [{a?: number}?], {leavesOnly: true}>;
expectType<'0.a'>(objectTupleLeaves);

declare const deepArrayLeaves: Paths<{a?: {b: readonly string[]}; c: boolean[]}, {leavesOnly: true}>;
expectType<`a.b.${number}` | `c.${number}`>(deepArrayLeaves);

declare const deepTupleLeaves: Paths<{a: {b: [string, number]}}, {leavesOnly: true}>;
expectType<'a.b.0' | 'a.b.1'>(deepTupleLeaves);

declare const deepObjectArrayLeaves: Paths<{a: {b: ReadonlyArray<{readonly c?: number; d: string}>}}, {leavesOnly: true}>;
expectType<`a.b.${number}.c` | `a.b.${number}.d`>(deepObjectArrayLeaves);

declare const deepObjectTupleLeaves: Paths<{a: {readonly b: [{readonly c: string}, {d?: number}]}}, {leavesOnly: true}>;
expectType<'a.b.0.c' | 'a.b.1.d'>(deepObjectTupleLeaves);

declare const nestedArrayLeaves: Paths<{a?: Array<Array<Array<{b: string}>>>}, {leavesOnly: true}>;
expectType<`a.${number}.${number}.${number}.b`>(nestedArrayLeaves);

declare const nestedTupleLeaves: Paths<{a: [[[{b: string}]]?]}, {leavesOnly: true}>;
expectType<'a.0.0.0.b'>(nestedTupleLeaves);

declare const trailingSpreadLeaves: Paths<[{a: string}, ...Array<{b: number}>], {leavesOnly: true}>;
expectType<'0.a' | `${number}.b`>(trailingSpreadLeaves);

declare const trailingSpreadLeaves1: Paths<[{a: string}, {b: number}, ...Array<{c?: number}>], {leavesOnly: true}>;
expectType<'0.a' | '1.b' | `${number}.c`>(trailingSpreadLeaves1);

declare const leadingSpreadLeaves: Paths<[...Array<{a?: string}>, {readonly b: number}], {leavesOnly: true}>;
expectType<`${number}.a` | `${number}.b`>(leadingSpreadLeaves);

declare const leadingSpreadLeaves1: Paths<[...Array<{a?: string}>, {readonly b: number}, {c: number}], {leavesOnly: true}>;
expectType<`${number}.a` | `${number}.b` | `${number}.c`>(leadingSpreadLeaves1);

declare const recursiveLeaves: Paths<RecursiveFoo, {leavesOnly: true; maxRecursionDepth: 10}>;
expectType<'foo.foo.foo.foo.foo.foo.foo.foo.foo.foo.foo'>(recursiveLeaves);

declare const recursiveWithMaxLeaves: Paths<RecursiveFoo, {maxRecursionDepth: 0; leavesOnly: true}>;
expectType<'foo'>(recursiveWithMaxLeaves);

declare const recursiveWithMaxLeaves1: Paths<RecursiveFoo, {maxRecursionDepth: 1; leavesOnly: true}>;
expectType<'foo.foo'>(recursiveWithMaxLeaves1);

declare const recursiveArrayLeaves: Paths<RecursionArray, {bracketNotation: true; maxRecursionDepth: 2; leavesOnly: true}>;
expectType<`[${number}][${number}][${number}]`>(recursiveArrayLeaves);

declare const bracketArrayLeaves: Paths<{a: Array<{b: string; c?: string}>}, {bracketNotation: true; leavesOnly: true}>;
expectType<`a[${number}].b` | `a[${number}].c`>(bracketArrayLeaves);

declare const bracketTupleLeaves: Paths<{a: [{b?: string}, {c: string}]}, {bracketNotation: true; leavesOnly: true}>;
expectType<'a[0].b' | 'a[1].c'>(bracketTupleLeaves);

declare const bracketNumericLeaves: Paths<{a: {1: string; 2: number}}, {bracketNotation: true; leavesOnly: true}>;
expectType<'a[1]' | 'a[2]'>(bracketNumericLeaves);

declare const bracketNestedArrayLeaves: Paths<{a: Array<Array<Array<{b: string}>>>}, {bracketNotation: true; leavesOnly: true}>;
expectType<`a[${number}][${number}][${number}].b`>(bracketNestedArrayLeaves);

declare const mapLeaves: Paths<{a: {b: Map<string, number>; c: ReadonlyMap<string, number>}; d: WeakMap<{a: string}, number>}, {leavesOnly: true}>;
expectType<'a.b' | 'a.c' | 'd'>(mapLeaves);

declare const setLeaves: Paths<{a: {b: Set<string>; c: ReadonlySet<string>}; d: WeakSet<{a: string}>}, {leavesOnly: true}>;
expectType<'a.b' | 'a.c' | 'd'>(setLeaves);

declare const unknownLeaves: Paths<{a: {b: unknown}}, {leavesOnly: true}>;
expectType<'a.b'>(unknownLeaves);

declare const anyLeaves: Paths<{a: {b: any}}, {leavesOnly: true}>;
expectType<'a.b'>(anyLeaves);

declare const neverLeaves: Paths<{a: {b: never}}, {leavesOnly: true}>;
expectType<'a.b'>(neverLeaves);

// -- depth option --
declare const zeroDepth: Paths<DeepObject, {depth: 0}>;
expectType<'a'>(zeroDepth);

declare const oneDepth: Paths<DeepObject, {depth: 1}>;
expectType<'a.b' | 'a.b2' | 'a.b3'>(oneDepth);

declare const twoDepth: Paths<DeepObject, {depth: 2}>;
expectType<'a.b.c' | `a.b2.${number}`>(twoDepth);

declare const threeDepth: Paths<DeepObject, {depth: 3}>;
expectType<'a.b.c.d'>(threeDepth);

declare const unionDepth: Paths<{a: {readonly b?: string}} | {x: {y?: {z: number}}}, {depth: 1}>;
expectType<'a.b' | 'x.y'>(unionDepth);

declare const unionDepth2: Paths<{a?: {b: string; readonly c: {d?: string}}} | {readonly x?: {y: string}}, {depth: 2}>;
expectType<'a.c.d'>(unionDepth2);

declare const unionDepth3: Paths<DeepObject, {depth: 0 | 3}>;
expectType<'a' | 'a.b.c.d'>(unionDepth3);

declare const unionDepth4: Paths<{a?: {b: string; readonly c: {d?: [string, number]}}} | {readonly x?: {y: string}}, {depth: 1 | 3}>;
expectType<'a.b' | 'a.c' | 'x.y' | 'a.c.d.0' | 'a.c.d.1'>(unionDepth4);

declare const unionDepth5: Paths<{a: {b?: string}} | {x: {y?: {z: number}}}, {depth: 0 | 2}>;
expectType<'a' | 'x' | 'x.y.z'>(unionDepth5);

declare const unreachableDepth: Paths<DeepObject, {depth: 4}>;
expectType<never>(unreachableDepth);

declare const unreachableDepth2: Paths<{a: {}}, {depth: 1}>;
expectType<never>(unreachableDepth2);

declare const unreachableDepth3: Paths<{a: readonly []}, {depth: 1}>;
expectType<never>(unreachableDepth3);

declare const unreachableAndReachableDepth: Paths<{a: {b: string}}, {depth: 1 | 2}>;
expectType<'a.b'>(unreachableAndReachableDepth);

declare const maxLessThanDepth: Paths<DeepObject, {maxRecursionDepth: 2; depth: 3}>;
expectType<never>(maxLessThanDepth);

declare const maxLessThanDepth2: Paths<RecursiveFoo, {depth: 12}>; // Default `maxRecursionDepth` is 10
expectType<never>(maxLessThanDepth2);

declare const maxSimilarToDepth: Paths<DeepObject, {maxRecursionDepth: 2; depth: 2}>;
expectType<'a.b.c' | `a.b2.${number}`>(maxSimilarToDepth);

declare const maxSimilarToDepth2: Paths<RecursiveFoo, {maxRecursionDepth: 0; depth: 0}>;
expectType<'foo'>(maxSimilarToDepth2);

declare const maxSimilarToDepth3: Paths<RecursiveFoo, {depth: 10; maxRecursionDepth: 10}>; // Default `maxRecursionDepth` is 10
expectType<'foo.foo.foo.foo.foo.foo.foo.foo.foo.foo.foo'>(maxSimilarToDepth3);

declare const maxMoreThanDepth: Paths<DeepObject, {maxRecursionDepth: 2; depth: 1}>;
expectType<'a.b' | 'a.b2' | 'a.b3'>(maxMoreThanDepth);

declare const maxMoreThanDepth2: Paths<RecursiveFoo, {maxRecursionDepth: 6; depth: 3}>;
expectType<'foo.foo.foo.foo'>(maxMoreThanDepth2);

declare const maxMoreAndLessThanDepth: Paths<RecursionArray, {maxRecursionDepth: 2; depth: 1 | 3}>;
expectType<`${number}.${number}`>(maxMoreAndLessThanDepth);

declare const maxLessAndSimilarThanDepth: Paths<RecursionArray, {maxRecursionDepth: 1; depth: 0 | 1}>;
expectType<number | `${number}` | `${number}.${number}`>(maxLessAndSimilarThanDepth);

declare const maxSimilarAndMoreThanDepth: Paths<RecursionArray, {maxRecursionDepth: 2; depth: 2 | 3}>;
expectType<`${number}.${number}.${number}`>(maxSimilarAndMoreThanDepth);

declare const noLeavesAtDepth: Paths<DeepObject, {leavesOnly: true; depth: 0}>;
expectType<never>(noLeavesAtDepth);

declare const onlyLeavesAtDepth: Paths<DeepObject, {leavesOnly: true; depth: 1}>;
expectType<'a.b3'>(onlyLeavesAtDepth);

declare const onlyLeavesAtDepth2: Paths<DeepObject, {leavesOnly: true; depth: 2}>;
expectType<`a.b2.${number}`>(onlyLeavesAtDepth2);

declare const deepArrayDepth: Paths<{a?: {b: readonly string[]}; c: boolean[]}, {depth: 0 | 2}>;
expectType<'a' | 'c' | `a.b.${number}`>(deepArrayDepth);

declare const deepTupleDepth: Paths<{a: {b: [string, number]}}, {depth: 2}>;
expectType<'a.b.0' | 'a.b.1'>(deepTupleDepth);

declare const deepObjectArrayDepth: Paths<{a: {b: ReadonlyArray<{readonly c?: number; d: string}>}}, {depth: 1 | 3}>;
expectType<'a.b' | `a.b.${number}.c` | `a.b.${number}.d`>(deepObjectArrayDepth);

declare const deepObjectTupleDepth: Paths<{a: {readonly b: [{readonly c: string}, {d?: [number]}]}}, {leavesOnly: true; depth: 3}>;
expectType<'a.b.0.c'>(deepObjectTupleDepth);

declare const nestedArrayDepth: Paths<{a?: Array<Array<Array<{b: string}>>>}, {depth: 1 | 2 | 3}>;
expectType<`a.${number}` | `a.${number}.${number}` | `a.${number}.${number}.${number}`>(nestedArrayDepth);

declare const nestedTupleDepth: Paths<{a: [[[{b: string}]]?]}, {depth: 0 | 4}>;
expectType<'a' | 'a.0.0.0.b'>(nestedTupleDepth);

declare const recursiveDepth: Paths<RecursiveFoo, {depth: 4}>;
expectType<'foo.foo.foo.foo.foo'>(recursiveDepth);

declare const recursiveDepth2: Paths<RecursiveFoo, {depth: 1 | 3 | 8; maxRecursionDepth: 10}>;
expectType<'foo.foo' | 'foo.foo.foo.foo' | 'foo.foo.foo.foo.foo.foo.foo.foo.foo'>(recursiveDepth2);

// For recursive types, leaves are at `maxRecursionDepth`
declare const recursiveDepth3: Paths<RecursiveFoo, {leavesOnly: true; depth: 5; maxRecursionDepth: 10}>;
expectType<never>(recursiveDepth3);

declare const recursiveDepth4: Paths<RecursiveFoo, {leavesOnly: true; depth: 5 | 10; maxRecursionDepth: 10}>; // No leaves at depth `5`
expectType<'foo.foo.foo.foo.foo.foo.foo.foo.foo.foo.foo'>(recursiveDepth4);

declare const recursiveDepth6: Paths<RecursiveFoo, {leavesOnly: true; maxRecursionDepth: 6; depth: 5}>; // Leaves are at depth `6`
expectType<never>(recursiveDepth6);

declare const maxLeavesAndDepth: Paths<DeepObject, {leavesOnly: true; maxRecursionDepth: 2; depth: 2}>; // All depth `2` paths are leaves
expectType<'a.b.c' | `a.b2.${number}`>(maxLeavesAndDepth);

declare const maxLeavesAndDepth2: Paths<DeepObject, {leavesOnly: true; maxRecursionDepth: 2; depth: 0 | 1 | 2}>;
expectType<'a.b3' | 'a.b.c' | `a.b2.${number}`>(maxLeavesAndDepth2);

declare const recursiveBracketDepth: Paths<RecursionArray, {bracketNotation: true; depth: 3}>;
expectType<`[${number}][${number}][${number}][${number}]`>(recursiveBracketDepth);

declare const recursiveBracketDepth2: Paths<RecursionArray, {bracketNotation: true; leavesOnly: true; depth: 3}>; // Leaves are at depth `10`
expectType<never>(recursiveBracketDepth2);

declare const bracketArrayDepth: Paths<{a: Array<{b: string; c?: string}>}, {bracketNotation: true; depth: 1 | 2}>;
expectType<`a[${number}]` | `a[${number}].b` | `a[${number}].c`>(bracketArrayDepth);

declare const bracketTupleDepth: Paths<{a: [{b?: string}, {c: string}]}, {bracketNotation: true; leavesOnly: true; depth: 0 | 2}>;
expectType<'a[0].b' | 'a[1].c'>(bracketTupleDepth);

declare const bracketNumericDepth: Paths<{a: {1: string; 2: number}}, {bracketNotation: true; depth: 1}>;
expectType<'a[1]' | 'a[2]'>(bracketNumericDepth);

declare const bracketNestedArrayDepth: Paths<{a: Array<Array<Array<{b: string}>>>}, {bracketNotation: true; depth: 2 | 4}>;
expectType<`a[${number}][${number}]` | `a[${number}][${number}][${number}].b`>(bracketNestedArrayDepth);

declare const trailingSpreadDepth: Paths<[{a: string}, ...Array<{b: number}>], {depth: 1}>;
expectType<'0.a' | `${number}.b`>(trailingSpreadDepth);

declare const leadingSpreadDepth: Paths<[...Array<{a?: string}>, {readonly b: number}], {depth: 0 | 1}>;
expectType<number | `${number}` | `${number}.b` | `${number}.a`>(leadingSpreadDepth);

declare const negativeDepth: Paths<DeepObject, {depth: -1}>;
expectType<never>(negativeDepth);

declare const positiveAndNegativeDepth: Paths<DeepObject, {depth: 1 | -1}>;
expectType<'a.b' | 'a.b2' | 'a.b3'>(positiveAndNegativeDepth);

declare const zeroPositiveAndNegativeDepth: Paths<DeepObject, {depth: 0 | 2 | -4}>;
expectType<'a' | 'a.b.c' | `a.b2.${number}`>(zeroPositiveAndNegativeDepth);

declare const neverDepth: Paths<DeepObject, {depth: never}>;
expectType<never>(neverDepth);

declare const anyDepth: Paths<DeepObject, {depth: any}>;
expectType<'a' | 'a.b.c' | `a.b2.${number}` | 'a.b3' | 'a.b' | 'a.b2' | 'a.b.c.d'>(anyDepth);

// Index signatures
declare const indexSignature: Paths<{[x: string]: {a: string; b: number}}>;
expectType<string>(indexSignature); // Collapsed union

declare const indexSignature1: Paths<{[x: Lowercase<string>]: {a: string; b: number}}>;
expectType<Lowercase<string> | `${Lowercase<string>}.a` | `${Lowercase<string>}.b`>(indexSignature1);

declare const indexSignature2: Paths<{[x: number]: {0: string; 1: number}}>;
expectType<number | `${number}` | `${number}.0` | `${number}.1`>(indexSignature2);

declare const indexSignature3: Paths<{[x: Uppercase<string>]: {a: string; b: number}}>;
expectType<Uppercase<string> | `${Uppercase<string>}.a` | `${Uppercase<string>}.b`>(indexSignature3);

declare const indexSignature4: Paths<{a: {[x: symbol]: {b: number; c: number}}}>;
expectType<'a'>(indexSignature4);

declare const indexSignatureWithStaticKeys: Paths<{[x: Uppercase<string>]: {a: string; b: number}; c: number}>;
expectType<'c' | Uppercase<string> | `${Uppercase<string>}.a` | `${Uppercase<string>}.b`>(indexSignatureWithStaticKeys);

declare const indexSignatureWithStaticKeys1: Paths<{[x: Uppercase<string>]: {a: string; b?: number}; C: {a: 'a'}}>;
expectType<Uppercase<string> | `${Uppercase<string>}.a` | `${Uppercase<string>}.b`>(indexSignatureWithStaticKeys1); // Collapsed union

declare const nonRootIndexSignature: Paths<{a: {[x: string]: {b: string; c: number}}}>;
expectType<'a' | `a.${string}` | `a.${string}.b` | `a.${string}.c`>(nonRootIndexSignature);

declare const nonRootIndexSignature1: Paths<{a: {[x: Lowercase<string>]: {b: string; c: number}}}>;
expectType<'a' | `a.${Lowercase<string>}` | `a.${Lowercase<string>}.b` | `a.${Lowercase<string>}.c`>(nonRootIndexSignature1);

declare const nestedIndexSignature: Paths<{[x: string]: {[x: Lowercase<string>]: {a: string; b: number}}}>;
expectType<string>(nestedIndexSignature);

declare const nestedIndexSignature1: Paths<{[x: Uppercase<string>]: {[x: Lowercase<string>]: {a: string; b: number}}}>;
expectType<Uppercase<string> | `${Uppercase<string>}.${Lowercase<string>}` | `${Uppercase<string>}.${Lowercase<string>}.a` | `${Uppercase<string>}.${Lowercase<string>}.b`>(
	nestedIndexSignature1,
);

declare const indexSignatureUnion: Paths<{a: {[x: string]: number} | {b: number}}>;
expectType<'a' | `a.${string}`>(indexSignatureUnion); // Collapsed union

declare const indexSignatureUnion1: Paths<{a: {[x: Uppercase<string>]: number} | {b: number}}>;
expectType<'a' | 'a.b' | `a.${Uppercase<string>}`>(indexSignatureUnion1);

declare const indexSignatureLeaves: Paths<{[x: string]: {a: string; b: number}}, {leavesOnly: true}>;
expectType<`${string}.a` | `${string}.b`>(indexSignatureLeaves);

declare const indexSignatureLeaves1: Paths<{a: {[x: string]: {b: string; c: number}}; d: string; e: {f: number}}, {leavesOnly: true}>;
expectType<`a.${string}.b` | `a.${string}.c` | 'd' | 'e.f'>(indexSignatureLeaves1);

declare const indexSignatureLeaves2: Paths<{a: {[x: string]: [] | {b: number}}}, {leavesOnly: true}>;
expectType<`a.${string}` | `a.${string}.b`>(indexSignatureLeaves2);

declare const indexSignatureDepth: Paths<{[x: string]: {a: string; b: number}}, {depth: 1}>;
expectType<`${string}.b` | `${string}.a`>(indexSignatureDepth);

declare const indexSignatureDepth1: Paths<{[x: string]: {a: string; b: number}}, {depth: 0}>;
expectType<string>(indexSignatureDepth1);

declare const indexSignatureDepth2: Paths<{[x: string]: {a: string; b: number}}, {depth: 0 | 1}>;
expectType<string>(indexSignatureDepth2); // Collapsed union

declare const indexSignatureDepth3: Paths<{a: {[x: string]: {b: string; c: number}}; d: string; e: {f: number}}, {depth: 0 | 2}>;
expectType<'a' | `a.${string}.b` | `a.${string}.c` | 'd' | 'e'>(indexSignatureDepth3);

declare const indexSignatureDepth4: Paths<{a: {[x: string]: [] | {b: number}}}, {depth: 2}>;
expectType<`a.${string}.b`>(indexSignatureDepth4);

declare const indexSignatureDepthLeaves: Paths<{a: {[x: string]: {b: string; c: number}}; d: string; e: {f: number}}, {depth: 0 | 2; leavesOnly: true}>;
expectType<`a.${string}.b` | `a.${string}.c` | 'd'>(indexSignatureDepthLeaves);

// Generic types
type PathsConstraint<T, _U extends Paths<T>> = never;

type Generic1<T> = {bar: {baz: T}};
type Test1<T> = PathsConstraint<Generic1<T>, 'bar.baz'>;

type Generic2<T, U> = {bar: {baz: {qux: T}; fizz: {buzz: U} | U | T}};
type Test2<T, U> = PathsConstraint<
	Generic2<T, U>,
	'bar' | 'bar.baz' | 'bar.baz.qux' | 'bar.fizz' | 'bar.fizz.buzz'
>;

type LeavesOnlyPathsConstraint<T, _U extends Paths<T, {leavesOnly: true}>> = never;

type Generic3<T> = {bar: {baz: T; qux: string}};
type Test3<T> = LeavesOnlyPathsConstraint<Generic3<T>, 'bar.qux'>;
// @ts-expect-error
type Test4<T> = LeavesOnlyPathsConstraint<Generic3<T>, 'bar'>; // 'bar' is not a leaf
// @ts-expect-error
type Test5<T> = LeavesOnlyPathsConstraint<Generic3<T>, 'bar.baz'>; // 'bar.baz' is not a leaf, because `T` is not known.

type DepthPathsConstraint<T, _U extends Paths<T, {depth: 1}>> = never;

type Generic4<T> = {bar: {baz: T}; qux: [T]};
type Test6<T> = DepthPathsConstraint<Generic4<T>, 'bar.baz' | 'qux.0'>;
// @ts-expect-error
type Test7<T> = DepthPathsConstraint<Generic4<T>, 'bar'>; // 'bar' is not at depth `1`
// @ts-expect-error
type Test8<T> = DepthPathsConstraint<Generic4<T>, 'qux'>; // 'qux' is not at depth `1`

type BracketNotationPathsConstraint<T, _U extends Paths<T, {bracketNotation: true}>> = never;

type Generic5<T> = {1: {2: T}; 3: [T]};
type Test9<T> = BracketNotationPathsConstraint<Generic5<T>, '[1]' | '[1][2]' | '[3]' | '[3][0]'>;

type MaxRecursionDepthPathsConstraint<T, _U extends Paths<T, {maxRecursionDepth: 2}>> = never;

type Generic6<T> = {foo: {bar: T}; baz: T; fizz: {buzz: {qux: {quxx: T}}}};
type Test10<T> = MaxRecursionDepthPathsConstraint<
	Generic6<T>,
	'foo' | 'foo.bar' | 'baz' | 'fizz' | 'fizz.buzz' | 'fizz.buzz.qux'
>;
// @ts-expect-error
type Test11<T> = MaxRecursionDepthPathsConstraint<Generic6<T>, 'fizz.buzz.qux.quxx'>; // 'fizz.buzz.qux.quxx' is at depth `3`

type LeavesOnlyAndDepthPathsConstraint<T, _U extends Paths<T, {leavesOnly: true; depth: 1}>> = never;

type Generic7<T> = {foo: {bar: T; baz: string; fizz: {buzz: number}}; qux: string};
type Test12<T> = LeavesOnlyAndDepthPathsConstraint<Generic7<T>, 'foo.baz'>;
// @ts-expect-error
type Test13<T> = LeavesOnlyAndDepthPathsConstraint<Generic7<T>, 'qux'>; // 'qux' is a leaf, but not at depth `1`.
// @ts-expect-error
type Test14<T> = LeavesOnlyAndDepthPathsConstraint<Generic7<T>, 'foo.fizz'>; // 'foo.fizz' is at depth `1`, but not a leaf.
// @ts-expect-error
type Test15<T> = LeavesOnlyAndDepthPathsConstraint<Generic7<T>, 'foo.bar'>; // 'foo.bar' is at depth `1`, but not a leaf because `T` is not known.
