// @flow

export interface Patch {
	op: "replace" | "remove" | "add";
	path: (string | number)[];
	value?: any;
}

export type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void

type Base = {...} | Array<any>
interface IProduce {
	/**
	 * Immer takes a state, and runs a function against it.
	 * That function can freely mutate the state, as it will create copies-on-write.
	 * This means that the original state will stay unchanged, and once the function finishes, the modified state is returned.
	 *
	 * If the first argument is a function, this is interpreted as the recipe, and will create a curried function that will execute the recipe
	 * any time it is called with the current state.
	 *
	 * @param currentState - the state to start with
	 * @param recipe - function that receives a proxy of the current state as first argument and which can be freely modified
	 * @param initialState - if a curried function is created and this argument was given, it will be used as fallback if the curried function is called with a state of undefined
	 * @returns The next state: a new state, or the current state if nothing was modified
	 */
	<S: Base>(
		currentState: S,
		recipe: (draftState: S) => S | void,
		patchListener?: PatchListener
	): S;
	// curried invocations with initial state
	<S: Base, A = void, B = void, C = void>(
		recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void,
		initialState: S
	): (currentState: S | void, a: A, b: B, c: C, ...extraArgs: any[]) => S;
	// curried invocations without initial state
	<S: Base, A = void, B = void, C = void>(
		recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void
	): (currentState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S;
}

interface IProduceWithPatches {
        /**
         * Like `produce`, but instead of just returning the new state,
         * a tuple is returned with [nextState, patches, inversePatches]
         *
         * Like produce, this function supports currying
         */
	<S: Base>(
		currentState: S,
		recipe: (draftState: S) => S | void
	): [S, Patch[], Patch[]];
	// curried invocations with initial state
	<S: Base, A = void, B = void, C = void>(
		recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void,
		initialState: S
	): (currentState: S | void, a: A, b: B, c: C, ...extraArgs: any[]) => [S, Patch[], Patch[]];
	// curried invocations without initial state
	<S: Base, A = void, B = void, C = void>(
		recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void
	): (currentState: S, a: A, b: B, c: C, ...extraArgs: any[]) => [S, Patch[], Patch[]];
}

declare export var produce: IProduce
declare export default IProduce

declare export var produceWithPatches: IProduceWithPatches

declare export var nothing: typeof undefined

declare export var immerable: Symbol

/**
 * Automatically freezes any state trees generated by immer.
 * This protects against accidental modifications of the state tree outside of an immer function.
 * This comes with a performance impact, so it is recommended to disable this option in production.
 * By default it is turned on during local development, and turned off in production.
 */
declare export function setAutoFreeze(autoFreeze: boolean): void

/**
 * Manually override whether proxies should be used.
 * By default done by using feature detection
 */
declare export function setUseProxies(useProxies: boolean): void

declare export function applyPatches<S>(state: S, patches: Patch[]): S

declare export function original<S>(value: S): S

declare export function current<S>(value: S): S

declare export function isDraft(value: any): boolean

/**
 * Creates a mutable draft from an (immutable) object / array.
 * The draft can be modified until `finishDraft` is called
 */
declare export function createDraft<T>(base: T): T

/**
 * Given a draft that was created using `createDraft`,
 * finalizes the draft into a new immutable object.
 * Optionally a patch-listener can be provided to gather the patches that are needed to construct the object.
 */
declare export function finishDraft<T>(base: T, listener?: PatchListener): T

declare export function enableES5(): void
declare export function enableMapSet(): void
declare export function enablePatches(): void
declare export function enableAllPlugins(): void

declare export function freeze<T>(obj: T, freeze?: boolean): T
