type PlainObjectKey = string | number | symbol;
type BasePlainObject = Record<PlainObjectKey, unknown>;
interface DefaultBasePlainObject extends BasePlainObject {
    readonly __tag: 'default-plain-object';
}
type Simplify<T> = {
    [P in keyof T]: T[P];
} & NonNullable<unknown>;
type PlainObjectDeepPartialUnknown<T> = {
    [P in keyof T]?: NonNullable<T[P]> extends BasePlainObject ? Simplify<PlainObjectDeepPartialUnknown<NonNullable<T[P]>>> : unknown;
};
type MsgOrErrorFactory = string | (() => Error);

type PlainObject<TValue extends BasePlainObject = DefaultBasePlainObject> = TValue extends DefaultBasePlainObject ? Record<PlainObjectKey, unknown> : Simplify<PlainObjectDeepPartialUnknown<TValue>>;

/**
 * Assert a value is a plain object
 *
 * @example
 * ```typescript
 * import { assertPlainObject } from '@httpx/plain-object';
 * import type { PlainObject } from '@httpx/plain-object';
 *
 * function fn(value: unknown) {
 *
 *     // 👇 Throws `new TypeError('Not a plain object')` if not a plain object
 *     assertPlainObject(value);
 *
 *     // 👇 Throws `new TypeError('Custom message')` if not a plain object
 *     assertPlainObject(value, 'Custom message');
 *
 *     // 👇 Throws custom error if not a plain object
 *     assertPlainObject(value, () => {
 *         throw new HttpBadRequest('Custom message');
 *     });
 *
 *     return value;
 * }
 *
 * try {
 *     const value = fn({ key: 'value' });
 *     // ✅ Value is known to be PlainObject<unknown>
 *     assertType<PlainObject>(value);
 * } catch (error) {
 *     console.error(error);
 * }
 * ```
 *
 * @throws TypeError
 */
declare function assertPlainObject<TValue extends BasePlainObject = DefaultBasePlainObject>(v: unknown, msgOrErrorFactory?: MsgOrErrorFactory): asserts v is TValue extends DefaultBasePlainObject ? BasePlainObject : PlainObject<TValue>;

/**
 * Check if a value is a plain object
 *
 * A plain object is a basic JavaScript object, such as {}, { data: [] }, new Object() or Object.create(null).
 *
 * @example
 * ```typescript
 * import { isPlainObject } from '@httpx/plain-object';
 *
 * // ✅👇 True
 *
 * isPlainObject({ });                       // ✅
 * isPlainObject({ key: 'value' });          // ✅
 * isPlainObject({ key: new Date() });       // ✅
 * isPlainObject(new Object());              // ✅
 * isPlainObject(Object.create(null));       // ✅
 * isPlainObject({ nested: { key: true} });  // ✅
 * isPlainObject(new Proxy({}, {}));         // ✅
 * isPlainObject({ [Symbol('tag')]: 'A' });  // ✅
 *
 * // ✅👇 (node context, workers, ...)
 * const runInNewContext = await import('node:vm').then(
 *     (mod) => mod.runInNewContext
 * );
 * isPlainObject(runInNewContext('({})'));   // ✅
 *
 * // ❌👇 False
 *
 * class Test { };
 * isPlainObject(new Test())           // ❌
 * isPlainObject(10);                  // ❌
 * isPlainObject(null);                // ❌
 * isPlainObject('hello');             // ❌
 * isPlainObject([]);                  // ❌
 * isPlainObject(new Date());          // ❌
 * isPlainObject(new Uint8Array([1])); // ❌
 * isPlainObject(Buffer.from('ABC'));  // ❌
 * isPlainObject(Promise.resolve({})); // ❌
 * isPlainObject(Object.create({}));   // ❌
 * isPlainObject(new (class Cls {}));  // ❌
 * isPlainObject(globalThis);          // ❌
 *
 * // ✅👇 Note that static built-in classes are treated as plain objects
 * //    check for `isStaticBuiltInClass` to exclude if needed
 *
 * isPlainObject(Math);                // ✅
 * isPlainObject(JSON);                // ✅
 * isPlainObject(Atomics);             // ✅
 * ```
 */
declare const isPlainObject: <TValue extends BasePlainObject = DefaultBasePlainObject>(v: unknown) => v is TValue extends DefaultBasePlainObject ? BasePlainObject : PlainObject<TValue>;

type StaticBuiltInClass = Math | JSON | Atomics;
declare const isStaticBuiltInClass: (v: unknown) => v is StaticBuiltInClass;

export { type PlainObject, type StaticBuiltInClass, assertPlainObject, isPlainObject, isStaticBuiltInClass };
