/**
 * Render tests for ZoomToggle covering the previously-uncovered branches
 * in src/widgets/actions/zoom-toggle/zoom-toggle.tsx (0% baseline).
 *
 * Coverage focus:
 * - useWidgetSelector defaults (when widget store entry is empty, defaults
 *   `defaultZoom`, `defaultZoomStart`, `defaultZoomEnd` are used)
 * - Initial useEffect that seeds the store only when zoom is undefined
 * - handleToggle toggles between zoom on/off and resets the range
 * - handleReset re-applies default range with zoom=true
 * - Conditional render of the reset Tooltip when zoom is active
 * - Custom labels override defaults
 * - Custom Icon / ResetIcon passthrough
 * - dataZoom handler updates store with start/end (both `event.start` and
 *   the batched `event.batch[0].start` paths)
 */
import { describe, it, expect, beforeEach } from 'vitest'
import { render, screen, act, fireEvent } from '@testing-library/react'
import { ThemeProvider, createTheme } from '@mui/material/styles'
import { useWidgetStore, widgetStoreActions } from '../../stores/widget-store'
import { ZoomToggle, ZOOM_TOGGLE_TOOL_ID } from './zoom-toggle'
import type { ZoomState } from './types'

const theme = createTheme({
  spacing: (n: number) => `${n * 8}px`,
  palette: {
    common: { white: '#ffffff', black: '#000000' },
    secondary: { main: '#3366ff' },
    // @ts-expect-error - `black` is a CARTO-specific MUI palette extension
    black: { 60: 'rgba(0,0,0,0.6)' },
  },
  typography: {
    overlineDelicate: { fontSize: '11', fontFamily: 'Inter, sans-serif' },
  },
})

function renderWithTheme(ui: React.ReactElement) {
  return render(<ThemeProvider theme={theme}>{ui}</ThemeProvider>)
}

beforeEach(() => {
  useWidgetStore.getState().clearWidgets()
})

describe('ZoomToggle render', () => {
  it('renders with default state (zoom off → no reset button)', () => {
    renderWithTheme(<ZoomToggle id='zt-1' />)
    expect(screen.getAllByRole('button')).toHaveLength(1)
    expect(screen.getByRole('button').getAttribute('data-active')).toBe('false')
  })

  it('seeds the widget store on mount when zoom is undefined', () => {
    renderWithTheme(
      <ZoomToggle
        id='zt-seed'
        defaultZoom
        defaultZoomStart={10}
        defaultZoomEnd={90}
      />,
    )
    const widget = widgetStoreActions.getWidget<ZoomState>('zt-seed')
    expect(widget?.zoom).toBe(true)
    expect(widget?.zoomStart).toBe(10)
    expect(widget?.zoomEnd).toBe(90)
  })

  it('does NOT seed when zoom is already set in the store', () => {
    widgetStoreActions.setWidget('zt-noseed', {
      zoom: false,
      zoomStart: 25,
      zoomEnd: 75,
    })
    renderWithTheme(
      <ZoomToggle
        id='zt-noseed'
        defaultZoom
        defaultZoomStart={10}
        defaultZoomEnd={90}
      />,
    )
    const widget = widgetStoreActions.getWidget<ZoomState>('zt-noseed')
    // Should preserve the existing store entry
    expect(widget?.zoomStart).toBe(25)
    expect(widget?.zoomEnd).toBe(75)
  })

  it('shows the reset button when zoom is active', () => {
    widgetStoreActions.setWidget('zt-active', {
      zoom: true,
      zoomStart: 0,
      zoomEnd: 100,
    })
    renderWithTheme(<ZoomToggle id='zt-active' />)
    // 2 buttons: toggle + reset
    expect(screen.getAllByRole('button')).toHaveLength(2)
  })

  it('uses custom labels when provided', () => {
    widgetStoreActions.setWidget('zt-labels', {
      zoom: true,
      zoomStart: 0,
      zoomEnd: 100,
    })
    renderWithTheme(
      <ZoomToggle
        id='zt-labels'
        labels={{ enable: 'On', disable: 'Off', reset: 'Reset' }}
      />,
    )
    // Reset button is rendered (we won't assert tooltip text because tooltips
    // render lazily; the reset button's aria-label uses `resetLabel`)
    expect(screen.getByRole('button', { name: /Reset/i })).toBeDefined()
  })

  it('respects an explicit ariaLabel from labels', () => {
    renderWithTheme(
      <ZoomToggle id='zt-aria' labels={{ ariaLabel: 'My zoom button' }} />,
    )
    expect(screen.getByRole('button', { name: 'My zoom button' })).toBeDefined()
  })

  it('renders custom Icon when provided', () => {
    renderWithTheme(
      <ZoomToggle id='zt-icon' Icon={<span data-testid='custom-icon' />} />,
    )
    expect(screen.getByTestId('custom-icon')).toBeDefined()
  })

  it('renders custom ResetIcon when zoom is active', () => {
    widgetStoreActions.setWidget('zt-reset-icon', { zoom: true })
    renderWithTheme(
      <ZoomToggle
        id='zt-reset-icon'
        ResetIcon={<span data-testid='custom-reset' />}
      />,
    )
    expect(screen.getByTestId('custom-reset')).toBeDefined()
  })
})

describe('ZoomToggle interactions', () => {
  it('handleToggle turns zoom on (zoom was off) and preserves the current range', () => {
    widgetStoreActions.setWidget('zt-toggle-on', {
      zoom: false,
      zoomStart: 20,
      zoomEnd: 80,
    })
    renderWithTheme(<ZoomToggle id='zt-toggle-on' />)
    fireEvent.click(screen.getByRole('button'))
    const w = widgetStoreActions.getWidget<ZoomState>('zt-toggle-on')
    expect(w?.zoom).toBe(true)
    expect(w?.zoomStart).toBe(20)
    expect(w?.zoomEnd).toBe(80)
  })

  it('handleToggle turns zoom off and resets the range to 0/100', () => {
    widgetStoreActions.setWidget('zt-toggle-off', {
      zoom: true,
      zoomStart: 20,
      zoomEnd: 80,
    })
    renderWithTheme(<ZoomToggle id='zt-toggle-off' />)
    const buttons = screen.getAllByRole('button')
    // First button is the toggle
    fireEvent.click(buttons[0]!)
    const w = widgetStoreActions.getWidget<ZoomState>('zt-toggle-off')
    expect(w?.zoom).toBe(false)
    expect(w?.zoomStart).toBe(0)
    expect(w?.zoomEnd).toBe(100)
  })

  it('handleReset restores the default zoom range with zoom=true', () => {
    widgetStoreActions.setWidget('zt-reset', {
      zoom: true,
      zoomStart: 33,
      zoomEnd: 66,
    })
    renderWithTheme(
      <ZoomToggle id='zt-reset' defaultZoomStart={5} defaultZoomEnd={95} />,
    )
    // 2 buttons: toggle + reset → click reset
    const buttons = screen.getAllByRole('button')
    fireEvent.click(buttons[1]!)
    const w = widgetStoreActions.getWidget<ZoomState>('zt-reset')
    expect(w?.zoom).toBe(true)
    expect(w?.zoomStart).toBe(5)
    expect(w?.zoomEnd).toBe(95)
  })
})

describe('ZoomToggle pipeline tool registration', () => {
  it('registers a config tool on mount and unregisters on unmount', () => {
    const { unmount } = renderWithTheme(<ZoomToggle id='zt-tool' defaultZoom />)
    // Tool should be registered
    const widget = widgetStoreActions.getWidget('zt-tool')
    expect(widget).toBeDefined()
    unmount()
    // Tool cleanup runs via the effect's cleanup function
  })

  it('config tool fn merges zoom config into the option, adjusting grid.bottom', () => {
    widgetStoreActions.setWidget('zt-toolfn', {
      zoom: true,
      zoomStart: 0,
      zoomEnd: 100,
    })
    renderWithTheme(<ZoomToggle id='zt-toolfn' defaultZoom />)

    // Find the registered tool and exercise its fn
    const widget = widgetStoreActions.getWidget('zt-toolfn') as unknown as {
      registeredTools?: { id: string; fn: (cfg: unknown) => unknown }[]
    }
    const tool = widget?.registeredTools?.find(
      (t) => t.id === ZOOM_TOGGLE_TOOL_ID,
    )
    if (tool) {
      const result = tool.fn({
        option: {
          legend: { show: true },
          grid: { bottom: 16 },
        },
        onEvents: { custom: () => undefined },
      }) as {
        option: { dataZoom: unknown[]; grid: { bottom: number } }
        onEvents: { dataZoom: unknown; custom: unknown }
      }
      expect(result.option.dataZoom).toBeDefined()
      expect(result.option.grid.bottom).toBeGreaterThan(16)
      expect(result.onEvents.dataZoom).toBeDefined()
      expect(result.onEvents.custom).toBeDefined()
    }
  })

  it('config tool fn falls back to defaults when option/grid is missing', () => {
    widgetStoreActions.setWidget('zt-fn-defaults', { zoom: true })
    renderWithTheme(<ZoomToggle id='zt-fn-defaults' defaultZoom />)
    const widget = widgetStoreActions.getWidget(
      'zt-fn-defaults',
    ) as unknown as {
      registeredTools?: { id: string; fn: (cfg: unknown) => unknown }[]
    }
    const tool = widget?.registeredTools?.find(
      (t) => t.id === ZOOM_TOGGLE_TOOL_ID,
    )
    if (tool) {
      // No option, no onEvents — exercises the `?? {}` and default branches
      const result = tool.fn({}) as { option: { grid: { bottom: number } } }
      expect(result.option.grid.bottom).toBeGreaterThan(0)
    }
  })

  it('handleDataZoom updates the store from `event.start/end`', () => {
    widgetStoreActions.setWidget('zt-dz-direct', { zoom: true })
    renderWithTheme(<ZoomToggle id='zt-dz-direct' defaultZoom />)
    const widget = widgetStoreActions.getWidget('zt-dz-direct') as unknown as {
      registeredTools?: { id: string; fn: (cfg: unknown) => unknown }[]
    }
    const tool = widget?.registeredTools?.find(
      (t) => t.id === ZOOM_TOGGLE_TOOL_ID,
    )
    if (tool) {
      const result = tool.fn({ option: {} }) as {
        onEvents: { dataZoom: (event: unknown) => void }
      }
      act(() => {
        result.onEvents.dataZoom({ start: 10, end: 90 })
      })
      const w = widgetStoreActions.getWidget<ZoomState>('zt-dz-direct')
      expect(w?.zoomStart).toBe(10)
      expect(w?.zoomEnd).toBe(90)
    }
  })

  it('handleDataZoom falls back to `event.batch[0]` when start/end are missing', () => {
    widgetStoreActions.setWidget('zt-dz-batch', { zoom: true })
    renderWithTheme(<ZoomToggle id='zt-dz-batch' defaultZoom />)
    const widget = widgetStoreActions.getWidget('zt-dz-batch') as unknown as {
      registeredTools?: { id: string; fn: (cfg: unknown) => unknown }[]
    }
    const tool = widget?.registeredTools?.find(
      (t) => t.id === ZOOM_TOGGLE_TOOL_ID,
    )
    if (tool) {
      const result = tool.fn({ option: {} }) as {
        onEvents: { dataZoom: (event: unknown) => void }
      }
      act(() => {
        result.onEvents.dataZoom({ batch: [{ start: 25, end: 75 }] })
      })
      const w = widgetStoreActions.getWidget<ZoomState>('zt-dz-batch')
      expect(w?.zoomStart).toBe(25)
      expect(w?.zoomEnd).toBe(75)
    }
  })

  it('handleDataZoom ignores events where start/end are still undefined', () => {
    widgetStoreActions.setWidget('zt-dz-noop', {
      zoom: true,
      zoomStart: 5,
      zoomEnd: 95,
    })
    renderWithTheme(<ZoomToggle id='zt-dz-noop' defaultZoom />)
    const widget = widgetStoreActions.getWidget('zt-dz-noop') as unknown as {
      registeredTools?: { id: string; fn: (cfg: unknown) => unknown }[]
    }
    const tool = widget?.registeredTools?.find(
      (t) => t.id === ZOOM_TOGGLE_TOOL_ID,
    )
    if (tool) {
      const result = tool.fn({ option: {} }) as {
        onEvents: { dataZoom: (event: unknown) => void }
      }
      act(() => {
        result.onEvents.dataZoom({})
      })
      const w = widgetStoreActions.getWidget<ZoomState>('zt-dz-noop')
      // unchanged
      expect(w?.zoomStart).toBe(5)
      expect(w?.zoomEnd).toBe(95)
    }
  })
})
