/** Union types of all values from a map type */
type ValueOf<T> = T[keyof T];

declare const OverwritePolicy: {
    readonly ALLOW: "ALLOW";
    readonly PROHIBIT: "PROHIBIT";
    readonly WARN: "WARN";
};
type OverwritePolicy = ValueOf<typeof OverwritePolicy>;

interface ItemWithValue<V> {
    /** item value */
    value: V;
}
interface ItemWithLoader<L> {
    /** function that returns value */
    loader: () => L;
}
interface RegistryState<V, L extends V | Promise<V>> {
    /**
     * If this is a global registry, it will be defined.
     */
    globalId?: string;
    /** name of this registry */
    name?: string;
    /** schema version, can be used to check compatibility */
    version: string;
    /**
     * fallback key to use if `.get()` was called without a key
     * This was the initial value when the registry was created.
     */
    initialDefaultKey?: string;
    /**
     * fallback key to use if `.get()` was called without a key
     * This is the current default key.
     */
    defaultKey?: string;
    /** set the first item registered as the default */
    setFirstItemAsDefault: boolean;
    /** define if registering with an existing key is allowed, prohibited or warned */
    overwritePolicy: OverwritePolicy;
    /** map to lookup items by key */
    items: {
        [key: string]: ItemWithValue<V> | ItemWithLoader<L>;
    };
    /** map to lookup promises by key */
    promises: {
        [key: string]: Promise<V>;
    };
}
interface RegistryConfig {
    /**
     * Set this value to define a global registry.
     * This will make it a true singleton and accessible via this `globalId` from any package.
     */
    globalId?: string;
    /** schema version, can be used to check compatibility */
    version?: string;
    /** name of this registry */
    name?: string;
    /** fallback key to use if `.get()` was called without a key */
    defaultKey?: string;
    /** set the first item registered as the default */
    setFirstItemAsDefault?: boolean;
    /** define if registering with an existing key is allowed, prohibited or warned */
    overwritePolicy?: OverwritePolicy;
}

/**
 * Registry class
 *
 * Can use generic to specify type of item in the registry
 * @type V Type of value
 * @type L Type of value returned from loader function when using `registerLoader()`.
 * `L` can be either `V`, `Promise<V>` or `V | Promise<V>`
 * Set `L=V` when does not support asynchronous loader.
 * By default `L` is set to `V | Promise<V>` to support
 * both synchronous and asynchronous loaders.
 */
declare class Registry<V, L extends V | Promise<V> = V | Promise<V>> {
    readonly state: RegistryState<V, L>;
    constructor(config?: RegistryConfig);
    /**
     * Clear all item in the registry.
     * Reset default key to initial default key (if any)
     * @returns the registry itself
     */
    clear(): this;
    /**
     * Apply a function to this registry.
     * @param func Function that takes this registry as an argument
     * @returns the registry itself
     */
    apply(func: (registry: this) => void): this;
    /**
     * Check if item with the given key exists
     * @param key the key to look for
     * @returns true if the item exists, false otherwise
     */
    has(key: string): boolean;
    /**
     * Register key with a value
     * @param key
     * @param value
     * @returns the registry itself
     */
    registerValue(key: string, value: V): this;
    /**
     * Register key with a loader, a function that returns a value.
     * @param key
     * @param loader
     * @returns the registry itself
     */
    registerLoader(key: string, loader: () => L): this;
    /**
     * Get value from the specified key.
     * If the item contains a loader, invoke the loader and return its output.
     * @param key
     */
    get(key?: string): V | L | undefined;
    /**
     * Similar to `.get()` but wrap results in a `Promise`.
     * This is useful when some items are async loaders to provide uniform output.
     * @param key
     */
    getAsPromise(key: string): Promise<V>;
    /**
     * Return the current default key.
     * Default key is a fallback key to use if `.get()` was called without a key.
     */
    getDefaultKey(): string | undefined;
    /**
     * Set default key to the specified key
     * Default key is a fallback key to use if `.get()` was called without a key.
     * @param key
     */
    setDefaultKey(key: string): this;
    /**
     * Remove default key.
     * Default key is a fallback key to use if `.get()` was called without a key.
     */
    clearDefaultKey(): this;
    /**
     * Return a map of all key-values in this registry.
     */
    getMap(): {
        [key: string]: V | L | undefined;
    };
    /**
     * Same with `.getMap()` but return a `Promise` that resolves when all values are resolved.
     */
    getMapAsPromise(): Promise<{
        [key: string]: V;
    }>;
    /**
     * Return all keys in this registry.
     */
    keys(): string[];
    /**
     * Return all values in this registry.
     * For loaders, they are invoked and their outputs are returned.
     */
    values(): (V | L | undefined)[];
    /**
     * Same with `.values()` but return a `Promise` that resolves when all values are resolved.
     */
    valuesAsPromise(): Promise<V[]>;
    /**
     * Return all key-value entries in this registry.
     */
    entries(): {
        key: string;
        value: V | L | undefined;
    }[];
    /**
     * Same with `.entries()` but return a `Promise` that resolves when all values are resolved.
     */
    entriesAsPromise(): Promise<{
        key: string;
        value: V;
    }[]>;
    /**
     * Remove the item with the specified key.
     * Do nothing if an item with the given key does not exist.
     * @param key
     */
    remove(key: string): this;
    /**
     * Get number of items in the registry
     */
    size(): number;
    /**
     * Returns true if there is no item in the registry
     */
    isEmpty(): boolean;
}

/**
 * Synchronous registry
 */
declare class SyncRegistry<V> extends Registry<V, V> {
}

/**
 * Helper function for creating a singleton
 * @param create factory function
 */
declare function makeSingleton<T>(create: () => T): () => T;

export { OverwritePolicy, Registry, type RegistryConfig, type RegistryState, SyncRegistry, makeSingleton };
