import { describe, it, expect, beforeEach, vi } from 'vitest'

import {
  initCanvasInteraction,
  setCanvasInteraction,
  getCanvasInteraction,
  subscribeToCanvasInteraction,
  getCanvasInteractionSnapshot,
  _resetCanvasInteraction,
} from './canvasInteractionStore.js'

describe('canvasInteractionStore', () => {
  beforeEach(() => {
    _resetCanvasInteraction()
  })

  it('starts with defaults: scrollAxis=both, zoomGestures=true', () => {
    expect(getCanvasInteraction()).toEqual({ scrollAxis: 'both', zoomGestures: true })
  })

  it('initCanvasInteraction seeds from config', () => {
    initCanvasInteraction({ scrollAxis: 'vertical', zoomGestures: false })
    expect(getCanvasInteraction()).toEqual({ scrollAxis: 'vertical', zoomGestures: false })
  })

  it('initCanvasInteraction with no args / empty / invalid resets to defaults', () => {
    initCanvasInteraction({ scrollAxis: 'vertical', zoomGestures: false })
    initCanvasInteraction()
    expect(getCanvasInteraction()).toEqual({ scrollAxis: 'both', zoomGestures: true })

    initCanvasInteraction({ scrollAxis: 'vertical', zoomGestures: false })
    initCanvasInteraction({})
    expect(getCanvasInteraction()).toEqual({ scrollAxis: 'both', zoomGestures: true })
  })

  it('initCanvasInteraction ignores invalid scrollAxis values', () => {
    initCanvasInteraction({ scrollAxis: 'sideways' })
    expect(getCanvasInteraction().scrollAxis).toBe('both')

    initCanvasInteraction({ scrollAxis: null })
    expect(getCanvasInteraction().scrollAxis).toBe('both')

    initCanvasInteraction({ scrollAxis: 42 })
    expect(getCanvasInteraction().scrollAxis).toBe('both')
  })

  it('initCanvasInteraction ignores non-boolean zoomGestures', () => {
    initCanvasInteraction({ zoomGestures: 'yes' })
    expect(getCanvasInteraction().zoomGestures).toBe(true)

    initCanvasInteraction({ zoomGestures: 1 })
    expect(getCanvasInteraction().zoomGestures).toBe(true)
  })

  it('accepts all four valid axis values', () => {
    for (const axis of ['both', 'vertical', 'horizontal', 'none']) {
      initCanvasInteraction({ scrollAxis: axis })
      expect(getCanvasInteraction().scrollAxis).toBe(axis)
    }
  })

  it('setCanvasInteraction patches partial state without resetting other keys', () => {
    initCanvasInteraction({ scrollAxis: 'vertical', zoomGestures: false })

    setCanvasInteraction({ zoomGestures: true })
    expect(getCanvasInteraction()).toEqual({ scrollAxis: 'vertical', zoomGestures: true })

    setCanvasInteraction({ scrollAxis: 'horizontal' })
    expect(getCanvasInteraction()).toEqual({ scrollAxis: 'horizontal', zoomGestures: true })
  })

  it('setCanvasInteraction with no-op patch does not notify subscribers', () => {
    const cb = vi.fn()
    subscribeToCanvasInteraction(cb)
    // Reset count: subscribers fire on initial reset_+ subscribe etc.
    cb.mockClear()

    setCanvasInteraction({}) // no keys
    setCanvasInteraction({ scrollAxis: 'invalid' }) // ignored
    setCanvasInteraction({ scrollAxis: 'both' }) // same as default
    setCanvasInteraction({ zoomGestures: true }) // same as default
    expect(cb).not.toHaveBeenCalled()

    setCanvasInteraction({ zoomGestures: false }) // real change
    expect(cb).toHaveBeenCalledTimes(1)
  })

  it('subscribers are notified on init and meaningful set; unsubscribe stops further notifications', () => {
    const cb = vi.fn()
    const unsub = subscribeToCanvasInteraction(cb)

    initCanvasInteraction({ scrollAxis: 'vertical' })
    setCanvasInteraction({ zoomGestures: false })

    expect(cb).toHaveBeenCalledTimes(2)
    unsub()

    setCanvasInteraction({ scrollAxis: 'horizontal' })
    expect(cb).toHaveBeenCalledTimes(2)
  })

  it('snapshot version changes on every meaningful mutation', () => {
    const v0 = getCanvasInteractionSnapshot()
    initCanvasInteraction({ scrollAxis: 'vertical' })
    const v1 = getCanvasInteractionSnapshot()
    expect(v1).not.toBe(v0)

    setCanvasInteraction({ zoomGestures: false })
    const v2 = getCanvasInteractionSnapshot()
    expect(v2).not.toBe(v1)
  })

  it('subscriber errors are isolated', () => {
    const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
    const goodCb = vi.fn()

    subscribeToCanvasInteraction(() => { throw new Error('boom') })
    subscribeToCanvasInteraction(goodCb)

    setCanvasInteraction({ zoomGestures: false })

    expect(goodCb).toHaveBeenCalled()
    expect(errorSpy).toHaveBeenCalled()
    errorSpy.mockRestore()
  })
})
