import EventEmitter from 'events';

/**
 * Environment that hosts the current State instance
 */
declare enum StateEnvironment {
    Popup = "popup",
    Background = "background",
    Content = "content",
    Offscreen = "offscreen"
}
/**
 * Configuration for the state
 */
declare type SetupConfig<T> = {
    persistent?: (keyof T)[];
    tabId?: number;
};
/**
 * Messages sent internally for synchronization
 */
declare type SyncMessage<T> = {
    type: "sync";
    action: "push" | "pull" | "tabId" | "pushStateOffscreen" | "pullStateOffscreen";
    data?: T;
    payload?: T;
    tabId: number;
};
declare type ChangeSource = "user" | "content" | "background" | "storage" | "sync" | "offscreen";

/**
 * Central state management class.
 */
declare class State<T extends object> extends EventEmitter {
    #private;
    setupDone: boolean;
    currentSource: ChangeSource;
    constructor(environment: StateEnvironment, initialState: T, config?: SetupConfig<T>);
    /**
     * Internal method to increase the ready progress
     * This is used to determine if the state is ready to be used
     *
     * Do not call this manually!
     */
    increaseReadyProgress(): void;
    /**
     * Get the current state as a proxy object.
     *
     * This allows direct modification of the object without helper classes like "set" or "merge".
     */
    get current(): T;
    /**
     * Gets the current state as a raw object.
     * Please note that this is NOT a proxy object. Do not modify it as changes will not be synced and may be overridden at any time
     */
    get currentRaw(): Readonly<T>;
    /**
     * Get the current tab ID that is used for syncing
     */
    get tabId(): number;
    /**
     * Get the current environment this instance is set to work in
     */
    get environment(): StateEnvironment;
    /**
     * Checks if a key should be persisted in the browser storage
     *
     * @param key Key to check
     * @returns
     */
    keyIsPersistent(key: keyof T): boolean;
    /**
     * Please the state object with new values without merging.
     * This is mostly used internally and should NOT be used for changing individual values.
     * Changes may not be synced - use the Proxy for that
     *
     * @param state New state object
     */
    replace(state: T, source: ChangeSource): void;
    /**
     * Destroy the instance and clean up.
     * This will remove all listeners and stops syncing to enable the instance to be garbage collected.
     *
     * After detroying the instance, it is not possible to use it anymore!
     */
    destroy(): void;
    /**
     * Check if the instance is destroyed
     */
    get isDestroyed(): boolean;
    /**
     * Ensure that the state is not already destroyed, otherwise throw
     */
    ensureNotDestroyed(): void;
    /**
     * Force the state to be pulled from other environments
     */
    forcePull(): Promise<void>;
}

/**
 * Use a plasmo-state instance inside a react component
 * This will automatically re-render when the state changes.
 *
 * The return value is the proxy of the state object. You can
 * directly modify its properties without needing to call a
 * function like "setState".
 *
 * Usage:
 * const myState = setupState(...);
 *
 * () => {
 *   const state = usePlasmoState(myState);
 *   return (
 *    <div>
 *      {state.couter}
 *      <button onClick={() => state.counter++}>Increment</button>
 *   </div>
 *   );
 * }
 *
 * @param state The plasmo-state instance
 * @returns The current state proxy
 */
declare function usePlasmoState<T extends object>(state: State<T>): T;

declare function setupState<T extends object>(environment: StateEnvironment, initialState: T, config?: SetupConfig<T>): State<T>;

export { ChangeSource, SetupConfig, State, StateEnvironment, SyncMessage, setupState as default, usePlasmoState };
