export type NoInfer<T> = [T][T extends any ? 0 : never];
export type IsAny<T, Y, N> = 1 extends 0 & T ? Y : N;
export type IsAnyBoolean<T> = 1 extends 0 & T ? true : false;
export type IsKnown<T, Y, N> = unknown extends T ? N : Y;
export type PickAsRequired<T, K extends keyof T> = Omit<T, K> &
	Required<Pick<T, K>>;
export type PickAsPartial<T, K extends keyof T> = Omit<T, K> &
	Partial<Pick<T, K>>;
export type PickUnsafe<T, K> = K extends keyof T ? Pick<T, K> : never;
export type PickExtra<T, K> = {
	[TKey in keyof K as string extends TKey
		? never
		: TKey extends keyof T
		? never
		: TKey]: K[TKey];
};

export type PickRequired<T> = {
	[K in keyof T as undefined extends T[K] ? never : K]: T[K];
};

export type Expand<T> = T extends object
	? T extends infer O
		? { [K in keyof O]: O[K] }
		: never
	: T;

export type UnionToIntersection<U> = (
	U extends any ? (k: U) => void : never
) extends (k: infer I) => any
	? I
	: never;

type Compute<T> = { [K in keyof T]: T[K] } | never;

type AllKeys<T> = T extends any ? keyof T : never;

export type MergeUnion<T, Keys extends keyof T = keyof T> = Compute<
	{
		[K in Keys]: T[Keys];
	} & {
		[K in AllKeys<T>]?: T extends any
			? K extends keyof T
				? T[K]
				: never
			: never;
	}
>;

export type Values<O> = O[ValueKeys<O>];
export type ValueKeys<O> = Extract<keyof O, PropertyKey>;

export type DeepAwaited<T> = T extends Promise<infer A>
	? DeepAwaited<A>
	: T extends Record<infer A, Promise<infer B>>
	? { [K in A]: DeepAwaited<B> }
	: T;

type DynamicParamIndicator = ":";
export type PathParamMask<TRoutePath extends string> =
	TRoutePath extends `${infer L}/${DynamicParamIndicator}${infer C}/${infer R}`
		? PathParamMask<`${L}/${string}/${R}`>
		: TRoutePath extends `${infer L}/${DynamicParamIndicator}${infer C}`
		? PathParamMask<`${L}/${string}`>
		: TRoutePath;

export type Timeout = ReturnType<typeof setTimeout>;

export type Updater<TPrevious, TResult = TPrevious> =
	| TResult
	| ((prev?: TPrevious) => TResult);

export type PickExtract<T, U> = {
	[K in keyof T as T[K] extends U ? K : never]: T[K];
};

export type PickExclude<T, U> = {
	[K in keyof T as T[K] extends U ? never : K]: T[K];
};

export function last<T>(arr: T[]) {
	return arr[arr.length - 1];
}

function isFunction(d: any): d is Function {
	return typeof d === "function";
}

export function functionalUpdate<TResult>(
	updater: Updater<TResult>,
	previous: TResult,
) {
	if (isFunction(updater)) {
		return updater(previous as TResult);
	}

	return updater;
}

export function pick<T, K extends keyof T>(parent: T, keys: K[]): Pick<T, K> {
	return keys.reduce((obj: any, key: K) => {
		obj[key] = parent[key];
		return obj;
	}, {} as any);
}

/**
 * This function returns `a` if `b` is deeply equal.
 * If not, it will replace any deeply equal children of `b` with those of `a`.
 * This can be used for structural sharing between immutable JSON values for example.
 * Do not use this with signals
 */
export function replaceEqualDeep<T>(prev: any, _next: T): T {
	if (prev === _next) {
		return prev;
	}

	const next = _next as any;

	const array = Array.isArray(prev) && Array.isArray(next);

	if (array || (isPlainObject(prev) && isPlainObject(next))) {
		const prevSize = array ? prev.length : Object.keys(prev).length;
		const nextItems = array ? next : Object.keys(next);
		const nextSize = nextItems.length;
		const copy: any = array ? [] : {};

		let equalItems = 0;

		for (let i = 0; i < nextSize; i++) {
			const key = array ? i : nextItems[i];
			copy[key] = replaceEqualDeep(prev[key], next[key]);
			if (copy[key] === prev[key]) {
				equalItems++;
			}
		}

		return prevSize === nextSize && equalItems === prevSize ? prev : copy;
	}

	return next;
}

// Copied from: https://github.com/jonschlinkert/is-plain-object
export function isPlainObject(o: any) {
	if (!hasObjectPrototype(o)) {
		return false;
	}

	// If has modified constructor
	const ctor = o.constructor;
	if (typeof ctor === "undefined") {
		return true;
	}

	// If has modified prototype
	const prot = ctor.prototype;
	if (!hasObjectPrototype(prot)) {
		return false;
	}

	// If constructor does not have an Object-specific method
	if (!prot.hasOwnProperty("isPrototypeOf")) {
		return false;
	}

	// Most likely a plain Object
	return true;
}

function hasObjectPrototype(o: any) {
	return Object.prototype.toString.call(o) === "[object Object]";
}

export function partialDeepEqual(a: any, b: any): boolean {
	if (a === b) {
		return true;
	}

	if (typeof a !== typeof b) {
		return false;
	}

	if (isPlainObject(a) && isPlainObject(b)) {
		return !Object.keys(b).some((key) => !partialDeepEqual(a[key], b[key]));
	}

	if (Array.isArray(a) && Array.isArray(b)) {
		return (
			a.length === b.length &&
			a.every((item, index) => partialDeepEqual(item, b[index]))
		);
	}

	return false;
}
