import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { Mock } from 'vitest'
import { devtools, redux } from 'zustand/middleware'
import { createStore } from 'zustand/vanilla'
import type { StoreApi } from 'zustand/vanilla'

type TupleOfEqualLengthH<
  Arr extends unknown[],
  T,
  Acc extends T[],
> = Arr extends [unknown, ...infer Rest]
  ? TupleOfEqualLengthH<Rest, T, [T, ...Acc]>
  : Acc
type TupleOfEqualLength<Arr extends unknown[], T> = number extends Arr['length']
  ? T[]
  : TupleOfEqualLengthH<Arr, T, []>

type Connection = {
  subscribers: ((message: unknown) => void)[]
  api: {
    subscribe: Mock<any>
    unsubscribe: Mock<any>
    send: Mock<any>
    init: Mock<any>
    error: Mock<any>
    dispatch?: Mock<any>
  }
}
const namedConnections = new Map<string | undefined, Connection>()
const unnamedConnections = new Map<string, Connection>()

function assertAllAreDefined<T>(arr: (T | undefined)[]): asserts arr is T[] {
  if (arr.some((e) => e === undefined)) {
    throw new Error()
  }
}
function getNamedConnectionApis<Keys extends (string | undefined)[]>(
  ...keys: Keys
) {
  const apis = keys.map((k) => namedConnections.get(k)?.api)
  assertAllAreDefined(apis)
  return apis as TupleOfEqualLength<Keys, Connection['api']>
}
function getNamedConnectionSubscribers<Keys extends (string | undefined)[]>(
  ...keys: Keys
) {
  const subscribers = keys.map((k) => {
    const subs = namedConnections.get(k)?.subscribers
    if (subs?.length !== 1) throw new Error()
    return subs[0]
  })
  assertAllAreDefined(subscribers)
  return subscribers as TupleOfEqualLength<
    Keys,
    Connection['subscribers'][number]
  >
}
function getUnnamedConnectionApis<Keys extends string[]>(...keys: Keys) {
  const apis = keys.map((k) => unnamedConnections.get(k)?.api)
  assertAllAreDefined(apis)
  return apis as TupleOfEqualLength<Keys, Connection['api']>
}
function getUnnamedConnectionSubscribers<Keys extends string[]>(...keys: Keys) {
  const subscribers = keys.map((k) => {
    const subs = unnamedConnections.get(k)?.subscribers
    if (!subs) {
      throw new Error()
    }
    return subs[0]
  })
  assertAllAreDefined(subscribers)
  return subscribers as TupleOfEqualLength<
    Keys,
    Connection['subscribers'][number]
  >
}

function getKeyFromOptions(options: any): string | undefined {
  let key: string | undefined = options?.name
  if (options?.testStore) {
    key = `${options?.name}|${options?.testStore}`
  }
  return key
}

const extensionConnector = {
  connect: vi.fn((options: any) => {
    const key = getKeyFromOptions(options)
    //console.log('options', options)
    const areNameUndefinedMapsNeeded =
      options.testConnectionId !== undefined && options?.name === undefined
    const connectionMap = areNameUndefinedMapsNeeded
      ? unnamedConnections
      : namedConnections
    const subscribers: Connection['subscribers'] = []
    const api = {
      subscribe: vi.fn((f: (m: unknown) => void) => {
        subscribers.push(f)
        return () => {}
      }),
      unsubscribe: vi.fn(() => {
        connectionMap.delete(
          areNameUndefinedMapsNeeded ? options.testConnectionId : key,
        )
      }),
      send: vi.fn(),
      init: vi.fn(),
      error: vi.fn(),
    }
    connectionMap.set(
      areNameUndefinedMapsNeeded ? options.testConnectionId : key,
      {
        subscribers,
        api,
      },
    )
    return api
  }),
}
;(window as any).__REDUX_DEVTOOLS_EXTENSION__ = extensionConnector

beforeEach(() => {
  vi.resetModules()
  extensionConnector.connect.mockClear()
  namedConnections.clear()
  unnamedConnections.clear()
})

it('connects to the extension by passing the options and initializes', async () => {
  const options = { name: 'test', foo: 'bar' }
  const initialState = { count: 0 }
  createStore(devtools(() => initialState, { enabled: true, ...options }))

  expect(extensionConnector.connect).toHaveBeenLastCalledWith(options)

  const [conn] = getNamedConnectionApis(options.name)
  expect(conn.init).toHaveBeenLastCalledWith(initialState)
})

describe('If there is no extension installed...', () => {
  let savedConsoleWarn: any
  beforeEach(() => {
    savedConsoleWarn = console.warn
    console.warn = vi.fn()
    ;(window as any).__REDUX_DEVTOOLS_EXTENSION__ = undefined
  })
  afterEach(() => {
    console.warn = savedConsoleWarn
    ;(window as any).__REDUX_DEVTOOLS_EXTENSION__ = extensionConnector
  })

  it('does not throw', async () => {
    expect(() => {
      createStore(devtools(() => ({ count: 0 })))
    }).not.toThrow()
  })

  it('does not warn', async () => {
    createStore(devtools(() => ({ count: 0 })))
    expect(console.warn).not.toBeCalled()
  })
})

describe('When state changes...', () => {
  it("sends { type: setStateName || 'anonymous`, ...rest } as the action with current state", async () => {
    const options = {
      name: 'testOptionsName',
      enabled: true,
    }
    const api = createStore(devtools(() => ({ count: 0, foo: 'bar' }), options))

    api.setState({ count: 10 }, false, 'testSetStateName')
    const [connection] = getNamedConnectionApis(options.name)
    expect(connection.send).toHaveBeenLastCalledWith(
      { type: 'testSetStateName' },
      { count: 10, foo: 'bar' },
    )

    api.setState({ count: 15 }, false, {
      type: 'testSetStateName',
      payload: 15,
    })
    expect(connection.send).toHaveBeenLastCalledWith(
      { type: 'testSetStateName', payload: 15 },
      { count: 15, foo: 'bar' },
    )

    api.setState({ count: 5, foo: 'baz' }, true)
    expect(connection.send).toHaveBeenLastCalledWith(
      { type: 'anonymous' },
      { count: 5, foo: 'baz' },
    )
  })
})

describe('When state changes with automatic setter inferring...', () => {
  it("sends { type: setStateName || 'setCount`, ...rest } as the action with current state", async () => {
    const options = {
      name: 'testOptionsName',
      enabled: true,
    }

    const api = createStore<{
      count: number
      setCount: (count: number) => void
    }>()(
      devtools(
        (set) => ({
          count: 0,
          setCount: (newCount: number) => {
            set({ count: newCount })
          },
        }),
        options,
      ),
    )

    api.getState().setCount(10)
    const [connection] = getNamedConnectionApis(options.name)
    expect(connection.send).toHaveBeenLastCalledWith(
      { type: expect.stringMatching(/^(Object\.setCount|anonymous)$/) },
      { count: 10, setCount: expect.any(Function) },
    )
  })
})

describe('when it receives a message of type...', () => {
  describe('ACTION...', () => {
    it('does nothing', async () => {
      const initialState = { count: 0 }
      const api = createStore(devtools(() => initialState, { enabled: true }))
      const setState = vi.spyOn(api, 'setState')

      const [subscriber] = getNamedConnectionSubscribers(undefined)
      subscriber({
        type: 'ACTION',
        payload: '{ "type": "INCREMENT" }',
      })

      expect(api.getState()).toBe(initialState)
      expect(setState).not.toBeCalled()
    })

    it('unless action type is __setState', async () => {
      const initialState = { count: 0 }
      const api = createStore(devtools(() => initialState, { enabled: true }))

      const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
      connectionSubscriber({
        type: 'ACTION',
        payload: '{ "type": "__setState", "state": { "foo": "bar" } }',
      })

      expect(api.getState()).toStrictEqual({ ...initialState, foo: 'bar' })
    })

    it('does nothing even if there is `api.dispatch`', async () => {
      const initialState = { count: 0 }
      const api = createStore(devtools(() => initialState, { enabled: true }))
      ;(api as any).dispatch = vi.fn()
      const setState = vi.spyOn(api, 'setState')

      const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
      connectionSubscriber({
        type: 'ACTION',
        payload: '{ "type": "INCREMENT" }',
      })

      expect(api.getState()).toBe(initialState)
      expect(setState).not.toBeCalled()
      expect((api as any).dispatch).not.toBeCalled()
    })

    it('dispatches with `api.dispatch` when `api.dispatchFromDevtools` is set to true', async () => {
      const initialState = { count: 0 }
      const api = createStore(devtools(() => initialState, { enabled: true }))
      ;(api as any).dispatch = vi.fn()
      ;(api as any).dispatchFromDevtools = true
      const setState = vi.spyOn(api, 'setState')

      const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
      connectionSubscriber({
        type: 'ACTION',
        payload: '{ "type": "INCREMENT" }',
      })

      expect(api.getState()).toBe(initialState)
      expect(setState).not.toBeCalled()
      expect((api as any).dispatch).toHaveBeenLastCalledWith({
        type: 'INCREMENT',
      })
    })

    it('does not throw for unsupported payload', async () => {
      const initialState = { count: 0 }
      const api = createStore(devtools(() => initialState, { enabled: true }))
      ;(api as any).dispatch = vi.fn()
      ;(api as any).dispatchFromDevtools = true
      const setState = vi.spyOn(api, 'setState')
      const originalConsoleError = console.error
      console.error = vi.fn()

      const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
      expect(() => {
        connectionSubscriber({
          type: 'ACTION',
          payload: 'this.increment()',
        })
      }).not.toThrow()

      expect(console.error).toHaveBeenLastCalledWith(
        '[zustand devtools middleware] Could not parse the received json',
        (() => {
          try {
            JSON.parse('this.increment()')
          } catch (e) {
            return e
          }
        })(),
      )

      expect(() => {
        connectionSubscriber({
          type: 'ACTION',
          payload: { name: 'increment', args: [] },
        })
      }).not.toThrow()

      expect(console.error).toHaveBeenLastCalledWith(
        '[zustand devtools middleware] Unsupported action format',
      )

      expect(api.getState()).toBe(initialState)
      expect(setState).not.toBeCalled()
      expect((api as any).dispatch).not.toBeCalled()

      console.error = originalConsoleError
    })
  })

  describe('DISPATCH and payload of type...', () => {
    it('RESET, it inits with initial state', async () => {
      const initialState = { count: 0 }
      const api = createStore(devtools(() => initialState, { enabled: true }))
      api.setState({ count: 1 })

      const [connection] = getNamedConnectionApis(undefined)
      connection.send.mockClear()
      const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
      connectionSubscriber({
        type: 'DISPATCH',
        payload: { type: 'RESET' },
      })

      expect(api.getState()).toStrictEqual(initialState)
      expect(connection.init).toHaveBeenLastCalledWith(initialState)
      expect(connection.send).not.toBeCalled()
    })

    it('COMMIT, it inits with current state', async () => {
      const initialState = { count: 0 }
      const api = createStore(devtools(() => initialState, { enabled: true }))
      api.setState({ count: 2 })
      const currentState = api.getState()

      const [connection] = getNamedConnectionApis(undefined)
      connection.send.mockClear()
      const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
      connectionSubscriber({
        type: 'DISPATCH',
        payload: { type: 'COMMIT' },
      })

      expect(connection.init).toHaveBeenLastCalledWith(currentState)
      expect(connection.send).not.toBeCalled()
    })

    describe('ROLLBACK...', () => {
      it('updates state without recording and inits with `message.state`', async () => {
        const initialState = { count: 0, increment: () => {} }
        const api = createStore(devtools(() => initialState, { enabled: true }))
        const newState = { foo: 'bar' }

        const [connection] = getNamedConnectionApis(undefined)
        connection.send.mockClear()
        const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
        connectionSubscriber({
          type: 'DISPATCH',
          payload: { type: 'ROLLBACK' },
          state: JSON.stringify(newState),
        })

        expect(api.getState()).toStrictEqual({ ...initialState, ...newState })
        expect(connection.init).toHaveBeenLastCalledWith({
          ...initialState,
          ...newState,
        })
        expect(connection.send).not.toBeCalled()
      })

      it('does not throw for unparsable `message.state`', async () => {
        const increment = () => {}
        const initialState = { count: 0, increment }
        const api = createStore(devtools(() => initialState, { enabled: true }))
        const originalConsoleError = console.error
        console.error = vi.fn()

        const [connection] = getNamedConnectionApis(undefined)
        connection.init.mockClear()
        connection.send.mockClear()
        const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
        connectionSubscriber({
          type: 'DISPATCH',
          payload: { type: 'ROLLBACK' },
          state: 'foobar',
        })

        expect(console.error).toHaveBeenLastCalledWith(
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('foobar')
            } catch (e) {
              return e
            }
          })(),
        )
        expect(api.getState()).toBe(initialState)
        expect(connection.init).not.toBeCalled()
        expect(connection.send).not.toBeCalled()

        console.error = originalConsoleError
      })
    })

    describe('JUMP_TO_STATE...', () => {
      const increment = () => {}
      it('updates state without recording with `message.state`', async () => {
        const initialState = { count: 0, increment }
        const api = createStore(devtools(() => initialState, { enabled: true }))
        const newState = { foo: 'bar' }

        const [connection] = getNamedConnectionApis(undefined)
        connection.send.mockClear()
        const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
        connectionSubscriber({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_STATE' },
          state: JSON.stringify(newState),
        })
        expect(api.getState()).toStrictEqual({ ...initialState, ...newState })
        expect(connection.send).not.toBeCalled()
      })

      it('does not throw for unparsable `message.state`', async () => {
        const initialState = { count: 0, increment: () => {} }
        const api = createStore(devtools(() => initialState, { enabled: true }))
        const originalConsoleError = console.error
        console.error = vi.fn()

        const [connection] = getNamedConnectionApis(undefined)
        connection.send.mockClear()
        const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
        connectionSubscriber({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_STATE' },
          state: 'foobar',
        })

        expect(console.error).toHaveBeenLastCalledWith(
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('foobar')
            } catch (e) {
              return e
            }
          })(),
        )
        expect(api.getState()).toBe(initialState)
        expect(connection.send).not.toBeCalled()

        console.error = originalConsoleError
      })
    })

    describe('JUMP_TO_ACTION...', () => {
      it('updates state without recording with `message.state`', async () => {
        const initialState = { count: 0, increment: () => {} }
        const api = createStore(devtools(() => initialState, { enabled: true }))
        const newState = { foo: 'bar' }

        const [connection] = getNamedConnectionApis(undefined)
        connection.send.mockClear()
        const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
        connectionSubscriber({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_ACTION' },
          state: JSON.stringify(newState),
        })
        expect(api.getState()).toStrictEqual({ ...initialState, ...newState })
        expect(connection.send).not.toBeCalled()
      })

      it('does not throw for unparsable `message.state`', async () => {
        const increment = () => {}
        const initialState = { count: 0, increment }
        const api = createStore(devtools(() => initialState, { enabled: true }))
        const originalConsoleError = console.error
        console.error = vi.fn()

        const [connection] = getNamedConnectionApis(undefined)
        connection.send.mockClear()
        const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
        connectionSubscriber({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_ACTION' },
          state: 'foobar',
        })

        expect(console.error).toHaveBeenLastCalledWith(
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('foobar')
            } catch (e) {
              return e
            }
          })(),
        )
        expect(api.getState()).toBe(initialState)
        expect(connection.send).not.toBeCalled()

        console.error = originalConsoleError
      })
    })

    it('IMPORT_STATE, it updates state without recording and inits the last computedState', async () => {
      const initialState = { count: 0, increment: () => {} }
      const api = createStore(devtools(() => initialState, { enabled: true }))
      const nextLiftedState = {
        computedStates: [{ state: { count: 4 } }, { state: { count: 5 } }],
      }

      const [connection] = getNamedConnectionApis(undefined)
      connection.send.mockClear()
      const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
      connectionSubscriber({
        type: 'DISPATCH',
        payload: {
          type: 'IMPORT_STATE',
          nextLiftedState,
        },
      })
      expect(api.getState()).toStrictEqual({
        ...initialState,
        ...nextLiftedState.computedStates.slice(-1)[0]?.state,
      })
      expect(connection.send).toHaveBeenLastCalledWith(null, nextLiftedState)
    })

    it('PAUSE_RECORDING, it toggles the sending of actions', async () => {
      const api = createStore(devtools(() => ({ count: 0 }), { enabled: true }))

      api.setState({ count: 1 }, false, 'increment')
      const [connection] = getNamedConnectionApis(undefined)
      const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
      expect(connection.send).toHaveBeenLastCalledWith(
        { type: 'increment' },
        { count: 1 },
      )
      connectionSubscriber({
        type: 'DISPATCH',
        payload: { type: 'PAUSE_RECORDING' },
      })

      api.setState({ count: 2 }, false, 'increment')
      expect(connection.send).toHaveBeenLastCalledWith(
        { type: 'increment' },
        { count: 1 },
      )
      connectionSubscriber({
        type: 'DISPATCH',
        payload: { type: 'PAUSE_RECORDING' },
      })

      api.setState({ count: 3 }, false, 'increment')
      expect(connection.send).toHaveBeenLastCalledWith(
        { type: 'increment' },
        { count: 3 },
      )
    })
  })
})

describe('with redux middleware', () => {
  let api: StoreApi<{
    count: number
    dispatch: (
      action: { type: 'INCREMENT' } | { type: 'DECREMENT' },
    ) => { type: 'INCREMENT' } | { type: 'DECREMENT' }
  }>

  it('works as expected', async () => {
    api = createStore(
      devtools(
        redux(
          (
            { count },
            { type }: { type: 'INCREMENT' } | { type: 'DECREMENT' },
          ) => ({
            count: count + (type === 'INCREMENT' ? 1 : -1),
          }),
          { count: 0 },
        ),
        { enabled: true },
      ),
    )
    ;(api as any).dispatch({ type: 'INCREMENT' })
    ;(api as any).dispatch({ type: 'INCREMENT' })
    const [connection] = getNamedConnectionApis(undefined)
    const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
    connectionSubscriber({
      type: 'ACTION',
      payload: JSON.stringify({ type: 'DECREMENT' }),
    })

    expect(connection.init.mock.calls).toMatchObject([
      [{ count: 0 }] as unknown as Record<string, unknown>,
    ])
    expect(connection.send.mock.calls).toMatchObject([
      [{ type: 'INCREMENT' }, { count: 1 }] as unknown as Record<
        string,
        unknown
      >,
      [{ type: 'INCREMENT' }, { count: 2 }] as unknown as Record<
        string,
        unknown
      >,
      [{ type: 'DECREMENT' }, { count: 1 }] as unknown as Record<
        string,
        unknown
      >,
    ])
    expect(api.getState()).toMatchObject({ count: 1 })
  })

  it('[DEV-ONLY] warns about misusage', () => {
    const originalConsoleWarn = console.warn
    console.warn = vi.fn()
    ;(api as any).dispatch({ type: '__setState' as any })
    expect(console.warn).toHaveBeenLastCalledWith(
      '[zustand devtools middleware] "__setState" action type is reserved ' +
        'to set state from the devtools. Avoid using it.',
    )

    console.warn = originalConsoleWarn
  })
})

describe('different envs', () => {
  let savedConsoleWarn: any
  beforeEach(() => {
    savedConsoleWarn = console.warn
    console.warn = vi.fn()
  })
  afterEach(() => {
    console.warn = savedConsoleWarn
  })

  it('works in non-browser env', async () => {
    const originalWindow = globalThis.window
    globalThis.window = undefined as any

    expect(() => {
      createStore(devtools(() => ({ count: 0 }), { enabled: true }))
    }).not.toThrow()

    globalThis.window = originalWindow
  })

  it('works in react native env', async () => {
    const originalWindow = globalThis.window
    globalThis.window = {} as any

    expect(() => {
      createStore(devtools(() => ({ count: 0 }), { enabled: true }))
    }).not.toThrow()

    globalThis.window = originalWindow
  })
})

it('preserves isRecording after setting from devtools', async () => {
  const api = createStore(devtools(() => ({ count: 0 }), { enabled: true }))
  const [connection] = getNamedConnectionApis(undefined)
  const [connectionSubscriber] = getNamedConnectionSubscribers(undefined)
  connectionSubscriber({
    type: 'DISPATCH',
    payload: { type: 'PAUSE_RECORDING' },
  })
  connectionSubscriber({
    type: 'ACTION',
    payload: '{ "type": "__setState", "state": { "foo": "bar" } }',
  })

  api.setState({ count: 1 })
  expect(connection.send).not.toBeCalled()
})

/* features:
 * [] if name is undefined - use multiple devtools connections.
 * [] if name and store is defined - use connection for specific 'name'.
 * [] if two stores are connected to one 'name' group and.
 *      another connected to another 'name' group, then feature should work
 * [] check actions with this feature, for multiple stores that store prefixes are added -
 * [] - reset
 * [] - commit
 * [] - rollback
 * [] - jump to state, jump to action
 * [] - import state
 **/

describe('when redux connection was called on multiple stores with `name` undefined in `devtools` options', () => {
  it('should create separate connection for each devtools store with .connect call', async () => {
    const options1 = { foo: 'bar', testConnectionId: 'asdf' }
    const options2 = { foo: 'barr', testConnectionId: '123asd' }
    const initialState1 = { count: 0 }
    const initialState2 = { count1: 1 }

    createStore(devtools(() => initialState1, { enabled: true, ...options1 }))
    createStore(devtools(() => initialState2, { enabled: true, ...options2 }))

    expect(extensionConnector.connect).toHaveBeenNthCalledWith(1, options1)
    expect(extensionConnector.connect).toHaveBeenNthCalledWith(2, options2)
  })

  it('should call .init on each different connection object', async () => {
    const options1 = { foo: 'bar', testConnectionId: 'asdf' }
    const options2 = { foo: 'barr', testConnectionId: '123asd' }
    const initialState1 = { count: 0 }
    const initialState2 = { count1: 1 }

    createStore(devtools(() => initialState1, { enabled: true, ...options1 }))
    createStore(devtools(() => initialState2, { enabled: true, ...options2 }))

    const [conn1, conn2] = getUnnamedConnectionApis(
      options1.testConnectionId,
      options2.testConnectionId,
    )
    expect(conn1.init).toHaveBeenCalledWith(initialState1)
    expect(conn2.init).toHaveBeenCalledWith(initialState2)
  })

  describe('when `store` property was provided in `devtools` call in options', () => {
    it('should create single connection for all internal calls of .connect and `store` is not passed to .connect', async () => {
      const { devtools: newDevtools } = await import('zustand/middleware')

      const options1 = { store: 'store1123', foo: 'bar1' }
      const options2 = { store: 'store2313132', foo: 'bar2' }
      const initialState1 = { count: 0 }
      const initialState2 = { count1: 1 }

      createStore(
        newDevtools(() => initialState1, { enabled: true, ...options1 }),
      )
      createStore(
        newDevtools(() => initialState2, { enabled: true, ...options2 }),
      )

      expect(extensionConnector.connect).toHaveBeenCalledTimes(1)
      expect(extensionConnector.connect).toHaveBeenCalledWith({
        foo: options1.foo,
      })
    })

    it('should call `.init` on single connection with combined states after each `create(devtools` call', async () => {
      const { devtools: newDevtools } = await import('zustand/middleware')

      const options1 = { store: 'store12' }
      const options2 = { store: 'store21' }
      const initialState1 = { count1: 0 }
      const initialState2 = { count2: 1 }

      createStore(
        newDevtools(() => initialState1, { enabled: true, ...options1 }),
      )
      createStore(
        newDevtools(() => initialState2, { enabled: true, ...options2 }),
      )

      expect(extensionConnector.connect).toHaveBeenCalledTimes(1)
      const [connection] = getNamedConnectionApis(undefined)
      expect(connection.init).toHaveBeenCalledTimes(2)
      expect(connection.init).toHaveBeenNthCalledWith(1, {
        [options1.store]: initialState1,
      })
      expect(connection.init).toHaveBeenNthCalledWith(2, {
        [options1.store]: initialState1,
        [options2.store]: initialState2,
      })
    })
  })
})

describe('when redux connection was called on multiple stores with `name` provided in `devtools` options', () => {
  describe('when same `name` is provided to all stores in devtools options', () => {
    it('should call .connect of redux extension with `name` that was passed from `devtools` options', async () => {
      const connectionName = 'test'
      const options1 = { name: connectionName, store: 'store1123', foo: 'bar1' }
      const options2 = { name: connectionName, store: 'store1414', foo: 'bar1' }
      const initialState1 = { count: 0 }
      const initialState2 = { count: 2 }

      createStore(devtools(() => initialState1, { enabled: true, ...options1 }))
      createStore(devtools(() => initialState2, { enabled: true, ...options2 }))

      expect(extensionConnector.connect).toHaveBeenCalledTimes(1)
      expect(extensionConnector.connect).toHaveBeenCalledWith({
        foo: options1.foo,
        name: connectionName,
      })
    })
  })

  describe('when different `name` props were provided for different group of stores in devtools options', () => {
    it('should call .connect of redux extension with `name` that was passed from `devtools` options', async () => {
      const connectionNameGroup1 = 'test1'
      const connectionNameGroup2 = 'test2'
      const options1 = {
        name: connectionNameGroup1,
        store: 'store1123',
        foo: 'bar2',
      }
      const options2 = {
        name: connectionNameGroup1,
        store: 'store1232',
        foo: 'bar3',
      }
      const options3 = {
        name: connectionNameGroup2,
        store: 'store61661',
        foo: 'bar4',
      }
      const options4 = {
        name: connectionNameGroup2,
        store: 'store14632',
        foo: 'bar5',
      }
      const initialState1 = { count: 0 }
      const initialState2 = { count: 2 }
      const initialState3 = { count: 5 }
      const initialState4 = { count: 7 }

      createStore(devtools(() => initialState1, { enabled: true, ...options1 }))
      createStore(devtools(() => initialState2, { enabled: true, ...options2 }))
      createStore(devtools(() => initialState3, { enabled: true, ...options3 }))
      createStore(devtools(() => initialState4, { enabled: true, ...options4 }))

      expect(extensionConnector.connect).toHaveBeenCalledTimes(2)
      expect(extensionConnector.connect).toHaveBeenNthCalledWith(1, {
        foo: options1.foo,
        name: connectionNameGroup1,
      })
      expect(extensionConnector.connect).toHaveBeenNthCalledWith(2, {
        foo: options3.foo,
        name: connectionNameGroup2,
      })
    })

    it('should call `.init` on single connection with combined states after each `create(devtools` call', async () => {
      const { devtools: newDevtools } = await import('zustand/middleware')
      const connectionNameGroup1 = 'test1'
      const connectionNameGroup2 = 'test2'
      const options1 = {
        name: connectionNameGroup1,
        store: 'store1123',
        foo: 'bar2',
      }
      const options2 = {
        name: connectionNameGroup1,
        store: 'store1232',
        foo: 'bar3',
      }
      const options3 = {
        name: connectionNameGroup2,
        store: 'store61661',
        foo: 'bar4',
      }
      const options4 = {
        name: connectionNameGroup2,
        store: 'store14632',
        foo: 'bar5',
      }
      const initialState1 = { count: 0 }
      const initialState2 = { count: 2 }
      const initialState3 = { count: 5 }
      const initialState4 = { count: 7 }

      createStore(
        newDevtools(() => initialState1, { enabled: true, ...options1 }),
      )
      createStore(
        newDevtools(() => initialState2, { enabled: true, ...options2 }),
      )
      createStore(
        newDevtools(() => initialState3, { enabled: true, ...options3 }),
      )
      createStore(
        newDevtools(() => initialState4, { enabled: true, ...options4 }),
      )

      expect(extensionConnector.connect).toHaveBeenCalledTimes(2)
      const [connection1, connection2] = getNamedConnectionApis(
        connectionNameGroup1,
        connectionNameGroup2,
      )
      expect(connection1.init).toHaveBeenCalledTimes(2)
      expect(connection1.init).toHaveBeenNthCalledWith(1, {
        [options1.store]: initialState1,
      })
      expect(connection1.init).toHaveBeenNthCalledWith(2, {
        [options1.store]: initialState1,
        [options2.store]: initialState2,
      })
      expect(connection2.init).toHaveBeenCalledTimes(2)
      expect(connection2.init).toHaveBeenNthCalledWith(1, {
        [options3.store]: initialState3,
      })
      expect(connection2.init).toHaveBeenNthCalledWith(2, {
        [options3.store]: initialState3,
        [options4.store]: initialState4,
      })
    })

    it('preserves isRecording after setting from devtools on proper connection subscriber', async () => {
      const options1 = { name: 'asdf1' }
      const options2 = { name: 'asdf2' }
      const api1 = createStore(
        devtools(() => ({ count: 0 }), { enabled: true, ...options1 }),
      )
      createStore(
        devtools(() => ({ count: 0 }), { enabled: true, ...options2 }),
      )
      const connections = getNamedConnectionApis(options1.name, options2.name)
      const [connectionSubscriber] = getNamedConnectionSubscribers(
        options1.name,
      )
      connectionSubscriber({
        type: 'DISPATCH',
        payload: { type: 'PAUSE_RECORDING' },
      })
      connectionSubscriber({
        type: 'ACTION',
        payload: '{ "type": "__setState", "state": { "foo": "bar" } }',
      })

      api1.setState({ count: 1 })
      connections.forEach((conn) => expect(conn.send).not.toBeCalled())
    })

    describe('with redux middleware', () => {
      let api1: StoreApi<{
        count: number
        dispatch: (
          action: { type: 'INCREMENT' } | { type: 'DECREMENT' },
        ) => { type: 'INCREMENT' } | { type: 'DECREMENT' }
      }>
      let api2: StoreApi<{
        count: number
        dispatch: (
          action: { type: 'INCREMENT' } | { type: 'DECREMENT' },
        ) => { type: 'INCREMENT' } | { type: 'DECREMENT' }
      }>

      it('works as expected', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        api1 = createStore(
          devtools(
            redux(
              (
                { count },
                { type }: { type: 'INCREMENT' } | { type: 'DECREMENT' },
              ) => ({
                count: count + (type === 'INCREMENT' ? 1 : -1),
              }),
              { count: 0 },
            ),
            { enabled: true, ...options1 },
          ),
        )
        api2 = createStore(
          devtools(
            redux(
              (
                { count },
                { type }: { type: 'INCREMENT' } | { type: 'DECREMENT' },
              ) => ({
                count: count + (type === 'INCREMENT' ? 1 : -1),
              }),
              { count: 10 },
            ),
            { enabled: true, ...options2 },
          ),
        )
        ;(api1 as any).dispatch({ type: 'INCREMENT' })
        ;(api1 as any).dispatch({ type: 'INCREMENT' })
        ;(api2 as any).dispatch({ type: 'INCREMENT' })
        ;(api2 as any).dispatch({ type: 'INCREMENT' })
        const [connection1, connection2] = getUnnamedConnectionApis(
          options1.testConnectionId,
          options2.testConnectionId,
        )
        const [connectionSubscriber1, connectionSubscriber2] =
          getUnnamedConnectionSubscribers(
            options1.testConnectionId,
            options2.testConnectionId,
          )
        connectionSubscriber1({
          type: 'ACTION',
          payload: JSON.stringify({ type: 'DECREMENT' }),
        })
        connectionSubscriber2({
          type: 'ACTION',
          payload: JSON.stringify({ type: 'DECREMENT' }),
        })

        expect(connection1.init.mock.calls).toMatchObject([
          [{ count: 0 }] as unknown as Record<string, unknown>,
        ])
        expect(connection2.init.mock.calls).toMatchObject([
          [{ count: 10 }] as unknown as Record<string, unknown>,
        ])
        expect(connection1.send.mock.calls).toMatchObject([
          [{ type: 'INCREMENT' }, { count: 1 }] as unknown as Record<
            string,
            unknown
          >,
          [{ type: 'INCREMENT' }, { count: 2 }] as unknown as Record<
            string,
            unknown
          >,
          [{ type: 'DECREMENT' }, { count: 1 }] as unknown as Record<
            string,
            unknown
          >,
        ])
        expect(connection2.send.mock.calls).toMatchObject([
          [{ type: 'INCREMENT' }, { count: 11 }] as unknown as Record<
            string,
            unknown
          >,
          [{ type: 'INCREMENT' }, { count: 12 }] as unknown as Record<
            string,
            unknown
          >,
          [{ type: 'DECREMENT' }, { count: 11 }] as unknown as Record<
            string,
            unknown
          > as unknown as Record<string, unknown>,
        ])
        expect(api1.getState()).toMatchObject({ count: 1 })
        expect(api2.getState()).toMatchObject({ count: 11 })
      })
    })
  })
})

describe('when create devtools was called multiple times with `name` option undefined', () => {
  describe('When state changes...', () => {
    it("sends { type: setStateName || 'anonymous`, ...rest } as the action with current state, isolated from other connections", async () => {
      const options1 = {
        enabled: true,
        testConnectionId: '123',
      }
      const options2 = {
        enabled: true,
        testConnectionId: '324',
      }
      const options3 = {
        enabled: true,
        testConnectionId: '412',
      }
      const api1 = createStore(
        devtools(() => ({ count: 0, foo: 'bar' }), options1),
      )
      createStore(devtools(() => ({ count: 0, foo: 'bar1' }), options2))
      createStore(devtools(() => ({ count: 0, foo: 'bar2' }), options3))

      api1.setState({ count: 10 }, false, 'testSetStateName')
      const [connection1, connection2, connection3] = getUnnamedConnectionApis(
        options1.testConnectionId,
        options2.testConnectionId,
        options3.testConnectionId,
      )
      expect(connection1.send).toHaveBeenLastCalledWith(
        { type: 'testSetStateName' },
        { count: 10, foo: 'bar' },
      )
      expect(connection2.send).not.toBeCalled()
      expect(connection3.send).not.toBeCalled()

      api1.setState({ count: 15 }, false, {
        type: 'testSetStateName',
        payload: 15,
      })
      expect(connection1.send).toHaveBeenLastCalledWith(
        { type: 'testSetStateName', payload: 15 },
        { count: 15, foo: 'bar' },
      )
      expect(connection2.send).not.toBeCalled()
      expect(connection3.send).not.toBeCalled()

      api1.setState({ count: 5, foo: 'baz' }, true)
      expect(connection1.send).toHaveBeenLastCalledWith(
        { type: 'anonymous' },
        { count: 5, foo: 'baz' },
      )
      expect(connection2.send).not.toBeCalled()
      expect(connection3.send).not.toBeCalled()
    })
  })

  describe('when it receives a message of type...', () => {
    describe('ACTION...', () => {
      it('does nothing, connections isolated from each other', async () => {
        const options1 = { testConnectionId: '123' }
        const options2 = { testConnectionId: '231' }
        const options3 = { testConnectionId: '4342' }
        const initialState1 = { count: 0 }
        const initialState2 = { count: 2 }
        const initialState3 = { count: 3 }
        const api1 = createStore(
          devtools(() => initialState1, {
            enabled: true,
            ...options1,
          }),
        )
        const api2 = createStore(
          devtools(() => initialState2, {
            enabled: true,
            ...options2,
          }),
        )
        const api3 = createStore(
          devtools(() => initialState3, {
            enabled: true,
            ...options3,
          }),
        )
        const setState1 = vi.spyOn(api1, 'setState')
        const setState2 = vi.spyOn(api2, 'setState')
        const setState3 = vi.spyOn(api3, 'setState')

        const [subscriber] = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
        )
        subscriber({
          type: 'ACTION',
          payload: '{ "type": "INCREMENT" }',
        })

        expect(api1.getState()).toBe(initialState1)
        expect(api2.getState()).toBe(initialState2)
        expect(api3.getState()).toBe(initialState3)
        expect(setState1).not.toBeCalled()
        expect(setState2).not.toBeCalled()
        expect(setState3).not.toBeCalled()
      })

      it('unless action type is __setState, connections isolated from each other', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const initialState1 = { count: 0 }
        const initialState2 = { count: 2 }
        const initialState3 = { count: 5 }
        const api1 = createStore(
          devtools(() => initialState1, { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => initialState2, { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => initialState3, { enabled: true, ...options3 }),
        )

        const [connectionSubscriber] = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
        )
        connectionSubscriber({
          type: 'ACTION',
          payload: '{ "type": "__setState", "state": { "foo": "bar" } }',
        })

        expect(api1.getState()).toStrictEqual({ ...initialState1, foo: 'bar' })
        expect(api2.getState()).toStrictEqual({ ...initialState2 })
        expect(api3.getState()).toStrictEqual({ ...initialState3 })
      })

      it('does nothing even if there is `api.dispatch`, connections isolated from each other', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const initialState1 = { count: 0 }
        const initialState2 = { count: 2 }
        const initialState3 = { count: 5 }
        const api1 = createStore(
          devtools(() => initialState1, { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => initialState2, { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => initialState3, { enabled: true, ...options3 }),
        )
        ;(api1 as any).dispatch = vi.fn()
        ;(api2 as any).dispatch = vi.fn()
        ;(api3 as any).dispatch = vi.fn()
        const setState1 = vi.spyOn(api1, 'setState')
        const setState2 = vi.spyOn(api2, 'setState')
        const setState3 = vi.spyOn(api3, 'setState')

        const subscribers = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        const testPayload = {
          type: 'ACTION',
          payload: '{ "type": "INCREMENT" }',
        }
        subscribers.forEach((sub) => sub(testPayload))

        expect(api1.getState()).toBe(initialState1)
        expect(api2.getState()).toBe(initialState2)
        expect(api3.getState()).toBe(initialState3)
        expect(setState1).not.toBeCalled()
        expect(setState2).not.toBeCalled()
        expect(setState3).not.toBeCalled()
        expect((api1 as any).dispatch).not.toBeCalled()
        expect((api2 as any).dispatch).not.toBeCalled()
        expect((api3 as any).dispatch).not.toBeCalled()
      })

      it('dispatches with `api.dispatch` when `api.dispatchFromDevtools` is set to true, connections are isolated from each other', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const initialState1 = { count: 0 }
        const initialState2 = { count: 2 }
        const initialState3 = { count: 5 }
        const api1 = createStore(
          devtools(() => initialState1, { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => initialState2, { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => initialState3, { enabled: true, ...options3 }),
        )
        ;(api1 as any).dispatch = vi.fn()
        ;(api1 as any).dispatchFromDevtools = true
        ;(api2 as any).dispatch = vi.fn()
        ;(api2 as any).dispatchFromDevtools = true
        ;(api3 as any).dispatch = vi.fn()
        ;(api3 as any).dispatchFromDevtools = true
        const setState1 = vi.spyOn(api1, 'setState')
        const setState2 = vi.spyOn(api2, 'setState')
        const setState3 = vi.spyOn(api3, 'setState')

        const subscribers = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        const getTestPayload = (n: number) => ({
          type: 'ACTION',
          payload: `{ "type": "INCREMENT${n}" }`,
        })
        subscribers.forEach((sub, i) => sub(getTestPayload(i + 1)))

        expect(api1.getState()).toBe(initialState1)
        expect(api2.getState()).toBe(initialState2)
        expect(api3.getState()).toBe(initialState3)
        expect(setState1).not.toBeCalled()
        expect(setState2).not.toBeCalled()
        expect(setState3).not.toBeCalled()
        expect((api1 as any).dispatch).toHaveBeenLastCalledWith({
          type: 'INCREMENT1',
        })
        expect((api2 as any).dispatch).toHaveBeenLastCalledWith({
          type: 'INCREMENT2',
        })
        expect((api3 as any).dispatch).toHaveBeenLastCalledWith({
          type: 'INCREMENT3',
        })
      })

      it('does not throw for unsupported payload, connections are isolated from each other', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const initialState1 = { count: 0 }
        const initialState2 = { count: 2 }
        const initialState3 = { count: 5 }
        const api1 = createStore(
          devtools(() => initialState1, { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => initialState2, { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => initialState3, { enabled: true, ...options3 }),
        )
        ;(api1 as any).dispatch = vi.fn()
        ;(api1 as any).dispatchFromDevtools = true
        ;(api2 as any).dispatch = vi.fn()
        ;(api2 as any).dispatchFromDevtools = true
        ;(api3 as any).dispatch = vi.fn()
        ;(api3 as any).dispatchFromDevtools = true
        const setState1 = vi.spyOn(api1, 'setState')
        const setState2 = vi.spyOn(api2, 'setState')
        const setState3 = vi.spyOn(api3, 'setState')
        const originalConsoleError = console.error
        console.error = vi.fn()

        const [
          connectionSubscriber1,
          connectionSubscriber2,
          connectionSubscriber3,
        ] = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        expect(() => {
          connectionSubscriber1({
            type: 'ACTION',
            payload: 'this.increment1()',
          })
        }).not.toThrow()
        expect(console.error).toHaveBeenNthCalledWith(
          1,
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('this.increment1()')
            } catch (e) {
              return e
            }
          })(),
        )

        expect(() => {
          connectionSubscriber1({
            type: 'ACTION',
            payload: 'this.increment2()',
          })
        }).not.toThrow()
        expect(console.error).toHaveBeenNthCalledWith(
          2,
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('this.increment2()')
            } catch (e) {
              return e
            }
          })(),
        )

        expect(() => {
          connectionSubscriber1({
            type: 'ACTION',
            payload: 'this.increment3()',
          })
        }).not.toThrow()
        expect(console.error).toHaveBeenNthCalledWith(
          3,
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('this.increment3()')
            } catch (e) {
              return e
            }
          })(),
        )

        expect(() => {
          connectionSubscriber1({
            type: 'ACTION',
            payload: { name: 'increment', args: [] },
          })
        }).not.toThrow()
        expect(console.error).toHaveBeenNthCalledWith(
          4,
          '[zustand devtools middleware] Unsupported action format',
        )
        expect(() => {
          connectionSubscriber2({
            type: 'ACTION',
            payload: { name: 'increment', args: [] },
          })
        }).not.toThrow()
        expect(console.error).toHaveBeenNthCalledWith(
          5,
          '[zustand devtools middleware] Unsupported action format',
        )
        expect(() => {
          connectionSubscriber3({
            type: 'ACTION',
            payload: { name: 'increment', args: [] },
          })
        }).not.toThrow()
        expect(console.error).toHaveBeenNthCalledWith(
          6,
          '[zustand devtools middleware] Unsupported action format',
        )

        expect(api1.getState()).toBe(initialState1)
        expect(api2.getState()).toBe(initialState2)
        expect(api3.getState()).toBe(initialState3)
        expect(setState1).not.toBeCalled()
        expect(setState2).not.toBeCalled()
        expect(setState3).not.toBeCalled()
        expect((api1 as any).dispatch).not.toBeCalled()
        expect((api2 as any).dispatch).not.toBeCalled()
        expect((api3 as any).dispatch).not.toBeCalled()

        console.error = originalConsoleError
      })
    })

    describe('DISPATCH and payload of type...', () => {
      it('RESET, it inits with initial state, connections are isolated from each other', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const initialState1 = { count: 0 }
        const initialState2 = { count: 2 }
        const initialState3 = { count: 5 }
        const api1 = createStore(
          devtools(() => initialState1, { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => initialState2, { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => initialState3, { enabled: true, ...options3 }),
        )
        api1.setState({ count: 1 })
        api2.setState({ count: 3 })
        api3.setState({ count: 10 })

        const connections = getUnnamedConnectionApis(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        const [connection1, connection2, connection3] = connections
        connections.forEach((conn) => conn.send.mockClear())
        const subscribers = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        const action = {
          type: 'DISPATCH',
          payload: { type: 'RESET' },
        }
        subscribers.forEach((sub) => sub(action))

        expect(api1.getState()).toStrictEqual(initialState1)
        expect(api1.getState()).toStrictEqual(initialState1)
        expect(api1.getState()).toStrictEqual(initialState1)
        expect(connection1.init).toHaveBeenLastCalledWith(initialState1)
        expect(connection2.init).toHaveBeenLastCalledWith(initialState2)
        expect(connection3.init).toHaveBeenLastCalledWith(initialState3)
        connections.forEach((conn) => expect(conn.send).not.toBeCalled())
      })

      it('COMMIT, it inits with current state, connections are isolated from each other', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const initialState1 = { count: 0 }
        const initialState2 = { count: 2 }
        const initialState3 = { count: 5 }
        const api1 = createStore(
          devtools(() => initialState1, { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => initialState2, { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => initialState3, { enabled: true, ...options3 }),
        )
        api1.setState({ count: 1 })
        api2.setState({ count: 3 })
        api3.setState({ count: 10 })
        const currentState1 = api1.getState()
        const currentState2 = api2.getState()
        const currentState3 = api3.getState()

        const connections = getUnnamedConnectionApis(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        connections.forEach((conn) => conn.send.mockClear())
        const subscribers = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        const action = {
          type: 'DISPATCH',
          payload: { type: 'COMMIT' },
        }
        subscribers.forEach((sub) => sub(action))

        const [connection1, connection2, connection3] = connections
        expect(connection1.init).toHaveBeenLastCalledWith(currentState1)
        expect(connection2.init).toHaveBeenLastCalledWith(currentState2)
        expect(connection3.init).toHaveBeenLastCalledWith(currentState3)
        connections.forEach((conn) => expect(conn.send).not.toBeCalled())
      })
    })

    describe('ROLLBACK...', () => {
      it('updates state without recording and inits with `message.state, connections are isolated from each other`', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const initialState1 = { count: 0, increment: () => {} }
        const initialState2 = { count: 2, increment: () => {} }
        const initialState3 = { count: 5, increment: () => {} }
        const api1 = createStore(
          devtools(() => initialState1, { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => initialState2, { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => initialState3, { enabled: true, ...options3 }),
        )
        const newState1 = { foo: 'bar1' }
        const newState2 = { foo: 'bar2' }
        const newState3 = { foo: 'bar3' }

        const connections = getUnnamedConnectionApis(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        connections.forEach((conn) => conn.send.mockClear())
        const [
          connectionSubscriber1,
          connectionSubscriber2,
          connectionSubscriber3,
        ] = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        connectionSubscriber1({
          type: 'DISPATCH',
          payload: { type: 'ROLLBACK' },
          state: JSON.stringify(newState1),
        })
        connectionSubscriber2({
          type: 'DISPATCH',
          payload: { type: 'ROLLBACK' },
          state: JSON.stringify(newState2),
        })
        connectionSubscriber3({
          type: 'DISPATCH',
          payload: { type: 'ROLLBACK' },
          state: JSON.stringify(newState3),
        })

        expect(api1.getState()).toStrictEqual({
          ...initialState1,
          ...newState1,
        })
        expect(api2.getState()).toStrictEqual({
          ...initialState2,
          ...newState2,
        })
        expect(api3.getState()).toStrictEqual({
          ...initialState3,
          ...newState3,
        })
        const [connection1, connection2, connection3] = connections
        expect(connection1.init).toHaveBeenLastCalledWith({
          ...initialState1,
          ...newState1,
        })
        expect(connection2.init).toHaveBeenLastCalledWith({
          ...initialState2,
          ...newState2,
        })
        expect(connection3.init).toHaveBeenLastCalledWith({
          ...initialState3,
          ...newState3,
        })
        connections.forEach((conn) => expect(conn.send).not.toBeCalled())
      })

      it('does not throw for unparsable `message.state`, connections are isolated from each other', async () => {
        const increment1 = () => {}
        const increment2 = () => {}
        const increment3 = () => {}
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const initialState1 = { count: 0, increment: increment1 }
        const initialState2 = { count: 2, increment: increment2 }
        const initialState3 = { count: 5, increment: increment3 }
        const api1 = createStore(
          devtools(() => initialState1, { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => initialState2, { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => initialState3, { enabled: true, ...options3 }),
        )
        const originalConsoleError = console.error
        console.error = vi.fn()

        const connections = getUnnamedConnectionApis(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        connections.forEach((conn) => conn.init.mockClear())
        connections.forEach((conn) => conn.send.mockClear())
        const [
          connectionSubscriber1,
          connectionSubscriber2,
          connectionSubscriber3,
        ] = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        connectionSubscriber1({
          type: 'DISPATCH',
          payload: { type: 'ROLLBACK' },
          state: 'foobar',
        })
        expect(console.error).toHaveBeenLastCalledWith(
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('foobar')
            } catch (e) {
              return e
            }
          })(),
        )
        connectionSubscriber2({
          type: 'DISPATCH',
          payload: { type: 'ROLLBACK' },
          state: 'foobar1',
        })
        expect(console.error).toHaveBeenLastCalledWith(
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('foobar1')
            } catch (e) {
              return e
            }
          })(),
        )
        connectionSubscriber3({
          type: 'DISPATCH',
          payload: { type: 'ROLLBACK' },
          state: 'foobar3',
        })
        expect(console.error).toHaveBeenLastCalledWith(
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('foobar3')
            } catch (e) {
              return e
            }
          })(),
        )

        expect(api1.getState()).toBe(initialState1)
        expect(api2.getState()).toBe(initialState2)
        expect(api3.getState()).toBe(initialState3)
        connections.forEach((conn) => {
          expect(conn.init).not.toBeCalled()
          expect(conn.send).not.toBeCalled()
        })

        console.error = originalConsoleError
      })
    })

    describe('JUMP_TO_STATE...', () => {
      const increment1 = () => {}
      const increment2 = () => {}
      const increment3 = () => {}

      it('updates state without recording with `message.state`, connections are isolated from each other', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const initialState1 = { count: 0, increment: increment1 }
        const initialState2 = { count: 2, increment: increment2 }
        const initialState3 = { count: 5, increment: increment3 }
        const api1 = createStore(
          devtools(() => initialState1, { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => initialState2, { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => initialState3, { enabled: true, ...options3 }),
        )
        const newState1 = { foo: 'bar1' }
        const newState2 = { foo: 'bar2' }
        const newState3 = { foo: 'bar3' }

        const connections = getUnnamedConnectionApis(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        connections.forEach((conn) => conn.send.mockClear())
        const [
          connectionSubscriber1,
          connectionSubscriber2,
          connectionSubscriber3,
        ] = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        connectionSubscriber1({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_STATE' },
          state: JSON.stringify(newState1),
        })
        connectionSubscriber2({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_STATE' },
          state: JSON.stringify(newState2),
        })
        connectionSubscriber3({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_STATE' },
          state: JSON.stringify(newState3),
        })

        expect(api1.getState()).toStrictEqual({
          ...initialState1,
          ...newState1,
        })
        expect(api2.getState()).toStrictEqual({
          ...initialState2,
          ...newState2,
        })
        expect(api3.getState()).toStrictEqual({
          ...initialState3,
          ...newState3,
        })
        connections.forEach((conn) => expect(conn.send).not.toBeCalled())
      })

      it('does not throw for unparsable `message.state`, connections are isolated from each other', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const initialState1 = { count: 0, increment: increment1 }
        const initialState2 = { count: 2, increment: increment2 }
        const initialState3 = { count: 5, increment: increment3 }
        const api1 = createStore(
          devtools(() => initialState1, { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => initialState2, { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => initialState3, { enabled: true, ...options3 }),
        )
        const originalConsoleError = console.error
        console.error = vi.fn()

        const connections = getUnnamedConnectionApis(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        connections.forEach((conn) => conn.send.mockClear())
        const [
          connectionSubscriber1,
          connectionSubscriber2,
          connectionSubscriber3,
        ] = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )

        connectionSubscriber1({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_STATE' },
          state: 'foobar',
        })
        expect(console.error).toHaveBeenLastCalledWith(
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('foobar')
            } catch (e) {
              return e
            }
          })(),
        )
        connectionSubscriber2({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_STATE' },
          state: 'foobar2',
        })
        expect(console.error).toHaveBeenLastCalledWith(
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('foobar2')
            } catch (e) {
              return e
            }
          })(),
        )
        connectionSubscriber3({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_STATE' },
          state: 'foobar3',
        })
        expect(console.error).toHaveBeenLastCalledWith(
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('foobar3')
            } catch (e) {
              return e
            }
          })(),
        )

        expect(api1.getState()).toBe(initialState1)
        expect(api2.getState()).toBe(initialState2)
        expect(api3.getState()).toBe(initialState3)
        connections.forEach((conn) => expect(conn.send).not.toBeCalled())

        console.error = originalConsoleError
      })
    })

    describe('JUMP_TO_ACTION...', () => {
      const increment1 = () => {}
      const increment2 = () => {}
      const increment3 = () => {}

      it('updates state without recording with `message.state`, connections are isolated from each other', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const initialState1 = { count: 0, increment: increment1 }
        const initialState2 = { count: 2, increment: increment2 }
        const initialState3 = { count: 5, increment: increment3 }
        const api1 = createStore(
          devtools(() => initialState1, { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => initialState2, { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => initialState3, { enabled: true, ...options3 }),
        )
        const newState1 = { foo: 'bar1' }
        const newState2 = { foo: 'bar2' }
        const newState3 = { foo: 'bar3' }

        const connections = getUnnamedConnectionApis(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        connections.forEach((conn) => conn.send.mockClear())
        const [
          connectionSubscriber1,
          connectionSubscriber2,
          connectionSubscriber3,
        ] = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )

        connectionSubscriber1({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_ACTION' },
          state: JSON.stringify(newState1),
        })
        connectionSubscriber2({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_ACTION' },
          state: JSON.stringify(newState2),
        })
        connectionSubscriber3({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_ACTION' },
          state: JSON.stringify(newState3),
        })

        expect(api1.getState()).toStrictEqual({
          ...initialState1,
          ...newState1,
        })
        expect(api2.getState()).toStrictEqual({
          ...initialState2,
          ...newState2,
        })
        expect(api3.getState()).toStrictEqual({
          ...initialState3,
          ...newState3,
        })
        connections.forEach((conn) => expect(conn.send).not.toBeCalled())
      })

      it('does not throw for unparsable `message.state`, connections are isolated from each other', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const initialState1 = { count: 0, increment: increment1 }
        const initialState2 = { count: 2, increment: increment2 }
        const initialState3 = { count: 5, increment: increment3 }
        const api1 = createStore(
          devtools(() => initialState1, { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => initialState2, { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => initialState3, { enabled: true, ...options3 }),
        )
        const originalConsoleError = console.error
        console.error = vi.fn()

        const connections = getUnnamedConnectionApis(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        connections.forEach((conn) => conn.send.mockClear())
        const [
          connectionSubscriber1,
          connectionSubscriber2,
          connectionSubscriber3,
        ] = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        connectionSubscriber1({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_ACTION' },
          state: 'foobar',
        })
        expect(console.error).toHaveBeenLastCalledWith(
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('foobar')
            } catch (e) {
              return e
            }
          })(),
        )
        connectionSubscriber2({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_ACTION' },
          state: 'foobar2',
        })
        expect(console.error).toHaveBeenLastCalledWith(
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('foobar2')
            } catch (e) {
              return e
            }
          })(),
        )
        connectionSubscriber3({
          type: 'DISPATCH',
          payload: { type: 'JUMP_TO_ACTION' },
          state: 'foobar3',
        })
        expect(console.error).toHaveBeenLastCalledWith(
          '[zustand devtools middleware] Could not parse the received json',
          (() => {
            try {
              JSON.parse('foobar3')
            } catch (e) {
              return e
            }
          })(),
        )

        expect(api1.getState()).toBe(initialState1)
        expect(api2.getState()).toBe(initialState2)
        expect(api3.getState()).toBe(initialState3)
        connections.forEach((conn) => expect(conn.send).not.toBeCalled())

        console.error = originalConsoleError
      })

      it('IMPORT_STATE, it updates state without recording and inits the last computedState, connections are isolated from each other', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const initialState1 = { count: 0, increment: increment1 }
        const initialState2 = { count: 2, increment: increment2 }
        const initialState3 = { count: 5, increment: increment3 }
        const api1 = createStore(
          devtools(() => initialState1, { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => initialState2, { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => initialState3, { enabled: true, ...options3 }),
        )
        const nextLiftedState1 = {
          computedStates: [{ state: { count: 4 } }, { state: { count: 5 } }],
        }
        const nextLiftedState2 = {
          computedStates: [{ state: { count: 20 } }, { state: { count: 8 } }],
        }
        const nextLiftedState3 = {
          computedStates: [{ state: { count: 12 } }, { state: { count: 100 } }],
        }

        const connections = getUnnamedConnectionApis(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )
        connections.forEach((conn) => conn.send.mockClear())
        const [
          connectionSubscriber1,
          connectionSubscriber2,
          connectionSubscriber3,
        ] = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )

        connectionSubscriber1({
          type: 'DISPATCH',
          payload: {
            type: 'IMPORT_STATE',
            nextLiftedState: nextLiftedState1,
          },
        })
        connectionSubscriber2({
          type: 'DISPATCH',
          payload: {
            type: 'IMPORT_STATE',
            nextLiftedState: nextLiftedState2,
          },
        })
        connectionSubscriber3({
          type: 'DISPATCH',
          payload: {
            type: 'IMPORT_STATE',
            nextLiftedState: nextLiftedState3,
          },
        })

        expect(api1.getState()).toStrictEqual({
          ...initialState1,
          ...nextLiftedState1.computedStates.slice(-1)[0]?.state,
        })
        expect(api2.getState()).toStrictEqual({
          ...initialState2,
          ...nextLiftedState2.computedStates.slice(-1)[0]?.state,
        })
        expect(api3.getState()).toStrictEqual({
          ...initialState3,
          ...nextLiftedState3.computedStates.slice(-1)[0]?.state,
        })
        const [connection1, connection2, connection3] = connections
        expect(connection1.send).toHaveBeenLastCalledWith(
          null,
          nextLiftedState1,
        )
        expect(connection2.send).toHaveBeenLastCalledWith(
          null,
          nextLiftedState2,
        )
        expect(connection3.send).toHaveBeenLastCalledWith(
          null,
          nextLiftedState3,
        )
      })

      it('PAUSE_RECORDING, it toggles the sending of actions, connections are isolated from each other', async () => {
        const options1 = { testConnectionId: 'asdf' }
        const options2 = { testConnectionId: '2f' }
        const options3 = { testConnectionId: 'd2e' }
        const api1 = createStore(
          devtools(() => ({ count: 0 }), { enabled: true, ...options1 }),
        )
        const api2 = createStore(
          devtools(() => ({ count: 2 }), { enabled: true, ...options2 }),
        )
        const api3 = createStore(
          devtools(() => ({ count: 4 }), { enabled: true, ...options3 }),
        )

        const newState1 = { count: 1 }
        const newState2 = { count: 12 }
        const newState3 = { count: 30 }
        api1.setState(newState1, false, 'increment')
        api2.setState(newState2, false, 'increment')
        api3.setState(newState3, false, 'increment')

        const [connection1, connection2, connection3] =
          getUnnamedConnectionApis(
            options1.testConnectionId,
            options2.testConnectionId,
            options3.testConnectionId,
          )
        const [
          connectionSubscriber1,
          connectionSubscriber2,
          connectionSubscriber3,
        ] = getUnnamedConnectionSubscribers(
          options1.testConnectionId,
          options2.testConnectionId,
          options3.testConnectionId,
        )

        expect(connection1.send).toHaveBeenLastCalledWith(
          { type: 'increment' },
          newState1,
        )
        connectionSubscriber1({
          type: 'DISPATCH',
          payload: { type: 'PAUSE_RECORDING' },
        })
        api1.setState({ count: 2 }, false, 'increment')
        expect(connection1.send).toHaveBeenLastCalledWith(
          { type: 'increment' },
          newState1,
        )
        connectionSubscriber1({
          type: 'DISPATCH',
          payload: { type: 'PAUSE_RECORDING' },
        })
        api1.setState({ count: 3 }, false, 'increment')
        expect(connection1.send).toHaveBeenLastCalledWith(
          { type: 'increment' },
          { count: 3 },
        )

        expect(connection2.send).toHaveBeenLastCalledWith(
          { type: 'increment' },
          newState2,
        )
        connectionSubscriber2({
          type: 'DISPATCH',
          payload: { type: 'PAUSE_RECORDING' },
        })
        api2.setState({ count: 2 }, false, 'increment')
        expect(connection2.send).toHaveBeenLastCalledWith(
          { type: 'increment' },
          newState2,
        )
        connectionSubscriber2({
          type: 'DISPATCH',
          payload: { type: 'PAUSE_RECORDING' },
        })
        api2.setState({ count: 3 }, false, 'increment')
        expect(connection2.send).toHaveBeenLastCalledWith(
          { type: 'increment' },
          { count: 3 },
        )

        expect(connection3.send).toHaveBeenLastCalledWith(
          { type: 'increment' },
          newState3,
        )
        connectionSubscriber3({
          type: 'DISPATCH',
          payload: { type: 'PAUSE_RECORDING' },
        })
        api3.setState({ count: 2 }, false, 'increment')
        expect(connection3.send).toHaveBeenLastCalledWith(
          { type: 'increment' },
          newState3,
        )
        connectionSubscriber3({
          type: 'DISPATCH',
          payload: { type: 'PAUSE_RECORDING' },
        })
        api3.setState({ count: 3 }, false, 'increment')
        expect(connection3.send).toHaveBeenLastCalledWith(
          { type: 'increment' },
          { count: 3 },
        )
      })
    })
  })
})

describe('when create devtools was called multiple times with `name` and `store` options defined', () => {
  describe('when `type` was provided in store state methods as option', () => {
    describe('When state changes...', () => {
      it("sends { type: setStateName || 'anonymous`, ...rest } as the action with current state", async () => {
        const options = {
          name: 'testOptionsName',
          store: 'someStore',
          enabled: true,
        }
        const api = createStore(
          devtools(() => ({ count: 0, foo: 'bar' }), options),
        )

        const testStateActionType = 'testSetStateName'

        api.setState({ count: 10 }, false, testStateActionType)
        const [connection] = getNamedConnectionApis(options.name)
        expect(connection.send).toHaveBeenLastCalledWith(
          { type: `${options.store}/${testStateActionType}` },
          { [options.store]: { count: 10, foo: 'bar' } },
        )

        api.setState({ count: 15 }, false, {
          type: testStateActionType,
          payload: 15,
        })
        expect(connection.send).toHaveBeenLastCalledWith(
          { type: `${options.store}/${testStateActionType}`, payload: 15 },
          { [options.store]: { count: 15, foo: 'bar' } },
        )

        api.setState({ count: 5, foo: 'baz' }, true)
        expect(connection.send).toHaveBeenLastCalledWith(
          { type: `${options.store}/anonymous` },
          { [options.store]: { count: 5, foo: 'baz' } },
        )
      })
    })

    describe('when it receives a message of type...', () => {
      describe('ACTION...', () => {
        it('does nothing, connections isolated from each other', async () => {
          const options1 = { testConnectionId: '123', store: 'store1' }
          const options2 = { testConnectionId: '231', store: 'store2' }
          const initialState1 = { count: 0 }
          const initialState2 = { count: 2 }
          const initialState3 = { count: 5 }
          const initialState4 = { count: 6 }
          const api1 = createStore(
            devtools(() => initialState1, {
              enabled: true,
              ...options1,
            }),
          )
          const api2 = createStore(
            devtools(() => initialState2, {
              enabled: true,
              ...options1,
            }),
          )
          const api3 = createStore(
            devtools(() => initialState3, {
              enabled: true,
              ...options2,
            }),
          )
          const api4 = createStore(
            devtools(() => initialState4, {
              enabled: true,
              ...options2,
            }),
          )
          const setState1 = vi.spyOn(api1, 'setState')
          const setState2 = vi.spyOn(api2, 'setState')
          const setState3 = vi.spyOn(api3, 'setState')
          const setState4 = vi.spyOn(api4, 'setState')

          const [subscriber] = getUnnamedConnectionSubscribers(
            options1.testConnectionId,
          )
          subscriber({
            type: 'ACTION',
            payload: '{ "type": "INCREMENT" }',
          })

          expect(api1.getState()).toBe(initialState1)
          expect(api2.getState()).toBe(initialState2)
          expect(api3.getState()).toBe(initialState3)
          expect(api4.getState()).toBe(initialState4)
          expect(setState1).not.toBeCalled()
          expect(setState2).not.toBeCalled()
          expect(setState3).not.toBeCalled()
          expect(setState4).not.toBeCalled()
        })

        it('unless action type is __setState, connections isolated from each other', async () => {
          const name1 = 'name1'
          const name2 = 'name2'
          const store1 = 'someStore1'
          const store2 = 'someStore2'
          const options1 = {
            name: name1,
            store: store1,
            testStore: store1,
          }
          const options2 = {
            name: name2,
            store: store2,
            testStore: store2,
          }
          const initialState1 = { count: 0 }
          const initialState2 = { count: 2 }
          const api1 = createStore(
            devtools(() => initialState1, { enabled: true, ...options1 }),
          )
          const api2 = createStore(
            devtools(() => initialState2, { enabled: true, ...options2 }),
          )
          const originalConsoleError = console.error
          console.error = vi.fn()

          const [connectionSubscriber] = getNamedConnectionSubscribers(
            getKeyFromOptions(options1),
          )
          connectionSubscriber({
            type: 'ACTION',
            payload:
              '{ "type": "__setState", "state": { "foo": "bar", "foo2": "bar2" } }',
          })

          expect(console.error).toHaveBeenCalledWith(
            expect.stringContaining(
              '[zustand devtools middleware] Unsupported __setState',
            ),
          )
          connectionSubscriber({
            type: 'ACTION',
            payload: `{ "type": "__setState", "state": { "${options1.store}": { "foo": "bar" } } }`,
          })

          expect(console.error).toHaveBeenCalledTimes(1)

          expect(api1.getState()).toStrictEqual({
            ...initialState1,
            foo: 'bar',
          })
          expect(api2.getState()).toStrictEqual({ ...initialState2 })

          console.error = originalConsoleError
        })

        it('does nothing even if there is `api.dispatch`, connections isolated from each other', async () => {
          const { devtools: newDevtools } = await import('zustand/middleware')

          const name1 = 'name1'
          const name2 = 'name2'
          const store1 = 'someStore1'
          const store2 = 'someStore2'
          const options1 = {
            name: name1,
            store: store1,
            testStore: store1,
          }
          const options2 = {
            name: name2,
            store: store2,
            testStore: store2,
          }
          const initialState1 = { count: 0 }
          const initialState2 = { count: 2 }
          const api1 = createStore(
            newDevtools(() => initialState1, { enabled: true, ...options1 }),
          )
          const api2 = createStore(
            newDevtools(() => initialState2, { enabled: true, ...options2 }),
          )
          ;(api1 as any).dispatch = vi.fn()
          ;(api2 as any).dispatch = vi.fn()
          const setState1 = vi.spyOn(api1, 'setState')
          const setState2 = vi.spyOn(api2, 'setState')

          const subscribers = getNamedConnectionSubscribers(
            getKeyFromOptions(options1),
            getKeyFromOptions(options2),
          )
          const testPayload = {
            type: 'ACTION',
            payload: '{ "type": "INCREMENT" }',
          }
          subscribers.forEach((sub) => sub(testPayload))

          expect(api1.getState()).toBe(initialState1)
          expect(api2.getState()).toBe(initialState2)
          expect(setState1).not.toBeCalled()
          expect(setState2).not.toBeCalled()
          expect((api1 as any).dispatch).not.toBeCalled()
          expect((api2 as any).dispatch).not.toBeCalled()
        })

        it('dispatches with `api.dispatch` when `api.dispatchFromDevtools` is set to true, connections are isolated from each other', async () => {
          const { devtools: newDevtools } = await import('zustand/middleware')
          const name1 = 'name1'
          const name2 = 'name2'
          const store1 = 'someStore1'
          const store2 = 'someStore2'
          const options1 = {
            name: name1,
            store: store1,
            testStore: store1,
          }
          const options2 = {
            name: name2,
            store: store2,
            testStore: store2,
          }
          const initialState1 = { count: 0 }
          const initialState2 = { count: 2 }
          const api1 = createStore(
            newDevtools(() => initialState1, { enabled: true, ...options1 }),
          )
          const api2 = createStore(
            newDevtools(() => initialState2, { enabled: true, ...options2 }),
          )
          ;(api1 as any).dispatch = vi.fn()
          ;(api1 as any).dispatchFromDevtools = true
          ;(api2 as any).dispatch = vi.fn()
          ;(api2 as any).dispatchFromDevtools = true
          const setState1 = vi.spyOn(api1, 'setState')
          const setState2 = vi.spyOn(api2, 'setState')

          const subscribers = getNamedConnectionSubscribers(
            getKeyFromOptions(options1),
            getKeyFromOptions(options2),
          )
          const getTestPayload = (n: number) => ({
            type: 'ACTION',
            payload: `{ "type": "INCREMENT${n}" }`,
          })
          subscribers.forEach((sub, i) => sub(getTestPayload(i + 1)))

          expect(api1.getState()).toBe(initialState1)
          expect(api2.getState()).toBe(initialState2)
          expect(setState1).not.toBeCalled()
          expect(setState2).not.toBeCalled()
          expect((api1 as any).dispatch).toHaveBeenLastCalledWith({
            type: 'INCREMENT1',
          })
          expect((api2 as any).dispatch).toHaveBeenLastCalledWith({
            type: 'INCREMENT2',
          })
        })
      })
    })
  })
})

describe('cleanup', () => {
  it('should unsubscribe from devtools when cleanup is called', async () => {
    const options = { name: 'test' }
    const store = createStore(devtools(() => ({ count: 0 }), options))
    const [connection] = getNamedConnectionApis(options.name)
    store.devtools.cleanup()

    expect(connection.unsubscribe).toHaveBeenCalledTimes(1)
  })

  it('should remove store from tracked connection after cleanup', async () => {
    const options = {
      name: 'test-store-name',
      store: 'test-store-id',
      enabled: true,
    }

    const store1 = createStore(devtools(() => ({ count: 0 }), options))
    store1.devtools.cleanup()
    const store2 = createStore(devtools(() => ({ count: 0 }), options))

    const [connection] = getNamedConnectionApis(options.name)

    store2.setState({ count: 15 }, false, 'updateCount')
    expect(connection.send).toHaveBeenLastCalledWith(
      { type: `${options.store}/updateCount` },
      { [options.store]: { count: 15 } },
    )

    store1.setState({ count: 20 }, false, 'ignoredAction')
    expect(connection.send).not.toHaveBeenLastCalledWith(
      { type: `${options.store}/ignoredAction` },
      expect.anything(),
    )
  })
})

describe('actionsDenylist', () => {
  it('should pass actionsDenylist option to Redux DevTools', async () => {
    const options = {
      name: 'test-filter',
      enabled: true,
      actionsDenylist: ['secret.*'],
    }

    createStore(devtools(() => ({ count: 0 }), options))

    // Verify that actionsDenylist was passed to the connect call
    const extensionConnector = (window as any).__REDUX_DEVTOOLS_EXTENSION__
    expect(extensionConnector.connect).toHaveBeenCalledWith(
      expect.objectContaining({
        actionsDenylist: ['secret.*'],
      }),
    )
  })
})
