import { describe, expect, test, vi, beforeEach } from 'vitest'
import userEvent from '@testing-library/user-event'
import { ZoomControls, type ZoomControlChildrenProps } from '../src'
import { ZoomControlsUI } from '../../react-ui/src/components'
import { render, within } from '../tests/providers'
import { MAP_ID } from '../tests/config'

describe('Zoom Controls', () => {
  const initialZoom = 3

  const mockSetZoom = vi.fn()
  const mockOnReset = vi.fn()

  beforeEach(() => {
    vi.clearAllMocks()
  })

  test('zoom in', async () => {
    const { container } = render(
      <Component initialZoom={initialZoom} mockSetZoom={mockSetZoom} />,
    )

    const zoomInButton = within(container).getByLabelText('Increase zoom')
    await userEvent.click(zoomInButton)

    expect(mockSetZoom).toHaveBeenCalledWith(initialZoom + 1)
  })

  test('zoom out', async () => {
    const { container } = render(
      <Component initialZoom={initialZoom} mockSetZoom={mockSetZoom} />,
    )

    const zoomOutButton = within(container).getByLabelText('Decrease zoom')
    await userEvent.click(zoomOutButton)

    expect(mockSetZoom).toHaveBeenCalledWith(initialZoom - 1)
  })

  test('reset', async () => {
    const { container } = render(
      <Component
        initialZoom={initialZoom}
        mockSetZoom={mockSetZoom}
        mockOnReset={mockOnReset}
      />,
    )

    const resetButton = within(container).getByLabelText('Reset action')
    await userEvent.click(resetButton)

    expect(mockOnReset).toHaveBeenCalled()
  })

  test('loading', () => {
    const { container } = render(
      <Component
        isLoading
        initialZoom={initialZoom}
        mockSetZoom={mockSetZoom}
        mockOnReset={mockOnReset}
      />,
    )

    expect(within(container).getByRole('progressbar')).toBeTruthy()
  })

  test('children render-props receive zoom, maxZoom, minZoom from the map store', () => {
    let captured: ZoomControlChildrenProps | null = null
    render(
      <ZoomControls mapId={MAP_ID}>
        {(props: ZoomControlChildrenProps) => {
          captured = props
          return null
        }}
      </ZoomControls>,
    )
    expect(captured).not.toBeNull()
    const args = captured as unknown as {
      zoom: number
      maxZoom: number
      minZoom: number
      isLoading: boolean
      setZoom: (z: number) => void
      flyTo: (vs: unknown) => void
      resetViewState: () => void
    }
    expect(typeof args.zoom).toBe('number')
    // maxZoom/minZoom may be unset in the test fixture's INITIAL_VIEW_STATE;
    // we just confirm the keys are propagated from the view-state store.
    expect('maxZoom' in args).toBe(true)
    expect('minZoom' in args).toBe(true)
    expect(typeof args.isLoading).toBe('boolean')
    expect(typeof args.setZoom).toBe('function')
    expect(typeof args.flyTo).toBe('function')
    expect(typeof args.resetViewState).toBe('function')
  })

  test('setZoom invokes the store action with mapId + zoom', () => {
    let captured: { setZoom: (z: number) => void } | null = null
    render(
      <ZoomControls mapId={MAP_ID}>
        {(props) => {
          captured = props
          return null
        }}
      </ZoomControls>,
    )
    expect(captured).not.toBeNull()
    // Exercise the useCallback closure — does not throw and reaches the store
    expect(() => captured!.setZoom(7)).not.toThrow()
  })

  test('flyTo invokes the store action with mapId + viewState', () => {
    let captured: {
      flyTo: (vs: { latitude: number; longitude: number; zoom: number }) => void
    } | null = null
    render(
      <ZoomControls mapId={MAP_ID}>
        {(props) => {
          captured = props
          return null
        }}
      </ZoomControls>,
    )
    expect(() =>
      captured!.flyTo({ latitude: 0, longitude: 0, zoom: 5 }),
    ).not.toThrow()
  })

  test('resetViewState invokes the store action with mapId', () => {
    let captured: { resetViewState: () => void } | null = null
    render(
      <ZoomControls mapId={MAP_ID}>
        {(props) => {
          captured = props
          return null
        }}
      </ZoomControls>,
    )
    expect(() => captured!.resetViewState()).not.toThrow()
  })
})

// Aux
interface ComponentProps {
  initialZoom: number
  isLoading?: boolean
  mockSetZoom: (zoom: number) => void
  mockOnReset?: () => void
}

const Component = ({
  initialZoom,
  isLoading = false,
  mockSetZoom,
  mockOnReset,
}: ComponentProps) => {
  return (
    <ZoomControls mapId={MAP_ID}>
      {(props: ZoomControlChildrenProps) => (
        <ZoomControlsUI
          zoom={initialZoom}
          maxZoom={props.maxZoom}
          minZoom={props.minZoom}
          isLoading={isLoading}
          onChange={mockSetZoom}
          onReset={mockOnReset}
        />
      )}
    </ZoomControls>
  )
}
