import { afterEach, describe, it, expect, vi } from 'vitest'
import { fireEvent, render, screen } from '@testing-library/react'
import { MenuItem } from '@mui/material'
import { LegendProvider } from '../../provider'
import { LegendRow } from './legend-row'
import { LegendGroup } from '../legend-group/legend-group'
import { LegendActions } from '../legend-actions/legend-actions'
import { LegendVisibilityToggle } from '../legend-visibility-toggle/legend-visibility-toggle'
import { LegendRowMenu } from '../legend-row-menu/legend-row-menu'
import { LegendZoomTo } from '../legend-row-menu/legend-zoom-to'
import {
  LegendShowOnlyLayer,
  LegendShowAllLayers,
} from '../legend-row-menu/legend-row-menu-items'
import { LegendOpacity } from '../legend-opacity/legend-opacity'
import { LegendConfigSelect } from '../legend-config-select/legend-config-select'
import { LegendItem } from '../legend-item/legend-item'
import {
  clearAllLegendStores,
  getLegendStore,
  type LegendLayerInput,
  type LegendVariable,
} from '../../stores'

const cat: LegendVariable = {
  type: 'category',
  items: [{ label: 'A', color: '#123456' }],
}

afterEach(() => clearAllLegendStores())

/**
 * Full row composition (the module shape): eye + ⋮ menu in the header slot
 * (menu last → rightmost), the inline opacity bar as first body child, then
 * config-select + item. Menu items: built-ins first, ZoomTo (app extra) last.
 */
function renderRow(
  id: string,
  layers: LegendLayerInput[],
  callbacks: { onZoomTo?: (id: string) => void | Promise<void> } = {},
  layerId = layers[0]!.id,
) {
  return render(
    <LegendProvider id={id} layers={layers}>
      <LegendRow layerId={layerId}>
        <LegendActions>
          <LegendVisibilityToggle />
          <LegendRowMenu>
            <LegendShowOnlyLayer />
            <LegendShowAllLayers />
            <LegendOpacity.Item />
            {callbacks.onZoomTo && (
              <LegendZoomTo onZoomTo={callbacks.onZoomTo} />
            )}
          </LegendRowMenu>
        </LegendActions>
        <LegendOpacity.Inline />
        <LegendConfigSelect />
        <LegendItem />
      </LegendRow>
    </LegendProvider>,
  )
}

/** The row's accordion header (title area), named by its content. */
const titleButton = (name: string | RegExp) =>
  screen.getByRole('button', { name })

describe('<LegendRow> composition', () => {
  it('partitions Actions into the header and other children into the body', () => {
    const { container } = renderRow('comp1', [
      { id: 'a', name: 'My layer', variables: [cat] },
    ])
    // Title from the store.
    expect(screen.getByText('My layer')).toBeTruthy()
    // The visibility action landed inside the header fade group…
    const fade = container.querySelector('.PsLegend-rowFade')!
    expect(fade.contains(screen.getByLabelText('Hide layer'))).toBe(true)
    // …the ⋮ too (it fades like the rest)…
    expect(fade.contains(screen.getByLabelText('Layer options'))).toBe(true)
    // …while the item renderer landed in the body, outside the fade group.
    expect(fade.contains(screen.getByText('A'))).toBe(false)
  })

  it('toggles visibility through the store', () => {
    renderRow('r1', [{ id: 'a', name: 'A', variables: [cat] }])
    fireEvent.click(screen.getByLabelText('Hide layer'))
    expect(getLegendStore('r1').getState().layers.a!.visible).toBe(false)
    fireEvent.click(screen.getByLabelText('Show layer'))
    expect(getLegendStore('r1').getState().layers.a!.visible).toBe(true)
  })

  it('clicking the title row toggles collapse (accordion header)', () => {
    renderRow('r2', [{ id: 'a', name: 'A', variables: [cat] }])
    const header = titleButton(/A/)
    expect(header.getAttribute('aria-expanded')).toBe('true')
    fireEvent.click(header)
    expect(getLegendStore('r2').getState().layers.a!.collapsed).toBe(true)
    expect(header.getAttribute('aria-expanded')).toBe('false')
    // Keyboard parity: Space re-expands.
    fireEvent.keyDown(header, { key: ' ' })
    expect(getLegendStore('r2').getState().layers.a!.collapsed).toBe(false)
  })

  it('offers no collapse affordance when the layer has nothing to collapse', () => {
    renderRow('r3', [{ id: 'a', name: 'Bodiless', variables: [] }])
    expect(screen.queryByRole('button', { name: /Bodiless/ })).toBeNull()
  })

  it('eye toggle drives collapse; a manual title-click collapse persists', () => {
    renderRow('r6', [{ id: 'a', name: 'A', variables: [cat] }])
    // Hide → collapses, and the title button disappears while hidden.
    fireEvent.click(screen.getByLabelText('Hide layer'))
    expect(getLegendStore('r6').getState().layers.a!.collapsed).toBe(true)
    expect(screen.queryByRole('button', { name: /A/ })).toBeNull()
    fireEvent.click(screen.getByLabelText('Show layer'))
    expect(getLegendStore('r6').getState().layers.a!.collapsed).toBe(false)

    // Manual collapse first → hide → show → stays collapsed.
    fireEvent.click(titleButton(/A/))
    fireEvent.click(screen.getByLabelText('Hide layer'))
    fireEvent.click(screen.getByLabelText('Show layer'))
    expect(getLegendStore('r6').getState().layers.a!.collapsed).toBe(true)
  })

  it('hides the subtitle while collapsed, restores it on expand', () => {
    renderRow('sub1', [
      { id: 'a', name: 'A', subtitle: 'Zoom 0–12', variables: [cat] },
    ])
    expect(screen.getByText('Zoom 0–12')).toBeTruthy()
    fireEvent.click(titleButton(/A/))
    expect(screen.queryByText('Zoom 0–12')).toBeNull()
    fireEvent.click(titleButton(/A/))
    expect(screen.getByText('Zoom 0–12')).toBeTruthy()
  })

  it('hides the subtitle while the layer is hidden (force-collapsed)', () => {
    renderRow('sub2', [
      { id: 'a', name: 'A', subtitle: 'Zoom 0–12', variables: [cat] },
    ])
    fireEvent.click(screen.getByLabelText('Hide layer'))
    expect(screen.queryByText('Zoom 0–12')).toBeNull()
    fireEvent.click(screen.getByLabelText('Show layer'))
    expect(screen.getByText('Zoom 0–12')).toBeTruthy()
  })

  it('renders the config select when the layer declares sections', () => {
    renderRow('r5', [
      {
        id: 'a',
        name: 'A',
        variables: [cat],
        sections: [{ id: 's1', options: ['x', 'y'], active: 'x' }],
      },
    ])
    // Closed field shows the first section's active value.
    expect(screen.getByRole('button', { name: 'Attribute' }).textContent).toBe(
      'x',
    )
  })

  it('stacks every variable of the layer with a single layer-level note', () => {
    renderRow('multi', [
      {
        id: 'a',
        name: 'Multi',
        variables: [
          cat,
          { type: 'proportion', min: 0, max: 100, attribute: 'revenue' },
        ],
        helperText: 'One note for the whole layer.',
      },
    ])
    // Both renderers are in the body…
    expect(screen.getByText('A')).toBeTruthy() // category item
    expect(screen.getByText('Radius range by')).toBeTruthy() // proportion header
    // …and the note renders exactly once, after all variables.
    expect(screen.getAllByText('One note for the whole layer.')).toHaveLength(1)
  })
})

describe('<LegendRow> grouped (inside Legend.Group)', () => {
  it('renders inside a group and collapses via its own title row', () => {
    render(
      <LegendProvider
        id='g1-row'
        groups={[{ id: 'g1', label: 'G' }]}
        layers={[{ id: 'a', name: 'Alpha', groupId: 'g1', variables: [cat] }]}
      >
        <LegendGroup groupId='g1'>
          <LegendRow layerId='a'>
            <LegendActions>
              <LegendVisibilityToggle />
            </LegendActions>
            <LegendItem />
          </LegendRow>
        </LegendGroup>
      </LegendProvider>,
    )
    fireEvent.click(screen.getByRole('button', { name: /Alpha/ }))
    expect(getLegendStore('g1-row').getState().layers.a!.collapsed).toBe(true)
  })
})

describe('<LegendRowMenu> (agnostic shell) + built-in items', () => {
  it('Legend.ZoomTo fires the composed handler with the layer id; the menu closes', async () => {
    const onZoomTo = vi.fn()
    renderRow('m3', [{ id: 'a', name: 'A', variables: [cat] }], { onZoomTo })
    fireEvent.click(screen.getByLabelText('Layer options'))
    fireEvent.click(screen.getByText('Zoom to layer'))
    expect(onZoomTo).toHaveBeenCalledWith('a')
    // Sync handler settles on the next microtask → item closes the menu.
    await vi.waitFor(() => {
      expect(screen.queryByRole('menuitem')).toBeNull()
    })
  })

  it('Legend.ZoomTo keeps the menu open with loading feedback while the handler is pending', async () => {
    let resolveZoom!: () => void
    const onZoomTo = vi.fn(
      () =>
        new Promise<void>((resolve) => {
          resolveZoom = resolve
        }),
    )
    renderRow('m3-busy', [{ id: 'a', name: 'A', variables: [cat] }], {
      onZoomTo,
    })
    fireEvent.click(screen.getByLabelText('Layer options'))
    fireEvent.click(screen.getByText('Zoom to layer'))

    expect(onZoomTo).toHaveBeenCalledWith('a')
    // Menu stays open; item shows the in-flight label and is disabled.
    const item = await screen.findByRole('menuitem', { name: /Loading/ })
    expect(item.getAttribute('aria-disabled')).toBe('true')
    expect(screen.getByRole('progressbar')).toBeDefined()

    resolveZoom()
    await vi.waitFor(() => {
      expect(screen.queryByRole('menuitem')).toBeNull()
    })
  })

  it('Legend.ZoomTo ignores a second click before the busy re-render', async () => {
    let resolveZoom!: () => void
    const onZoomTo = vi.fn(
      () =>
        new Promise<void>((resolve) => {
          resolveZoom = resolve
        }),
    )
    renderRow('m3-dbl', [{ id: 'a', name: 'A', variables: [cat] }], {
      onZoomTo,
    })
    fireEvent.click(screen.getByLabelText('Layer options'))
    const zoomItem = screen.getByText('Zoom to layer')
    // Two clicks in the same tick — before React applies setBusy(true).
    fireEvent.click(zoomItem)
    fireEvent.click(zoomItem)

    expect(onZoomTo).toHaveBeenCalledTimes(1)

    resolveZoom()
    await vi.waitFor(() => {
      expect(screen.queryByRole('menuitem')).toBeNull()
    })
  })

  it('Show only this layer / Show all layers dispatch the sibling actions', () => {
    render(
      <LegendProvider
        id='m7'
        layers={[
          { id: 'a', name: 'A', variables: [cat] },
          { id: 'b', name: 'B', variables: [cat] },
        ]}
      >
        <LegendRow layerId='a'>
          <LegendActions>
            <LegendRowMenu>
              <LegendShowOnlyLayer />
              <LegendShowAllLayers />
            </LegendRowMenu>
          </LegendActions>
          <LegendItem />
        </LegendRow>
      </LegendProvider>,
    )
    fireEvent.click(screen.getByLabelText('Layer options'))
    fireEvent.click(screen.getByText('Show only this layer'))
    expect(getLegendStore('m7').getState().layers.b!.visible).toBe(false)

    fireEvent.click(screen.getByLabelText('Layer options'))
    fireEvent.click(screen.getByText('Show all layers'))
    expect(getLegendStore('m7').getState().layers.b!.visible).toBe(true)
  })

  it('show only/all scope to the group bucket and restore collapse snapshots', () => {
    render(
      <LegendProvider
        id='m9'
        groups={[{ id: 'g1', label: 'G' }]}
        layers={[
          { id: 'a', name: 'A', groupId: 'g1', variables: [cat] },
          { id: 'b', name: 'B', groupId: 'g1', variables: [cat] },
          { id: 'loose', name: 'Loose', variables: [cat] },
        ]}
      >
        <LegendGroup groupId='g1'>
          <LegendRow layerId='a'>
            <LegendActions>
              <LegendRowMenu>
                <LegendShowOnlyLayer />
                <LegendShowAllLayers />
              </LegendRowMenu>
            </LegendActions>
            <LegendItem />
          </LegendRow>
        </LegendGroup>
      </LegendProvider>,
    )
    // Manual collapse on the sibling: the snapshot must survive the cycle.
    getLegendStore('m9').getState().setCollapsed('b', true)

    fireEvent.click(screen.getByLabelText('Layer options'))
    fireEvent.click(screen.getByText('Show only this layer'))
    let st = getLegendStore('m9').getState()
    expect(st.layers.b!.visible).toBe(false) // sibling in g1
    expect(st.layers.loose!.visible).toBe(true) // other bucket untouched

    fireEvent.click(screen.getByLabelText('Layer options'))
    fireEvent.click(screen.getByText('Show all layers'))
    st = getLegendStore('m9').getState()
    expect(st.layers.b!.visible).toBe(true)
    expect(st.layers.b!.collapsed).toBe(true) // manual collapse restored
  })

  it('renders any consumer MenuItem; clicks dispatch and close the menu', () => {
    const onCustom = vi.fn()
    render(
      <LegendProvider
        id='m5'
        layers={[{ id: 'a', name: 'A', variables: [cat] }]}
      >
        <LegendRow layerId='a'>
          <LegendActions>
            <LegendRowMenu>
              <MenuItem onClick={onCustom}>Custom thing</MenuItem>
            </LegendRowMenu>
          </LegendActions>
        </LegendRow>
      </LegendProvider>,
    )
    fireEvent.click(screen.getByLabelText('Layer options'))
    fireEvent.click(screen.getByText('Custom thing'))
    expect(onCustom).toHaveBeenCalled()
    expect(screen.queryByRole('menuitem')).toBeNull()
  })

  it('renders nothing without children', () => {
    render(
      <LegendProvider
        id='m6'
        layers={[{ id: 'a', name: 'A', variables: [cat] }]}
      >
        <LegendRow layerId='a'>
          <LegendActions>
            <LegendRowMenu />
          </LegendActions>
        </LegendRow>
      </LegendProvider>,
    )
    expect(screen.queryByLabelText('Layer options')).toBeNull()
  })

  it('stays available while hidden, with only the applicable items', () => {
    renderRow(
      'm8',
      [{ id: 'a', name: 'A', variables: [cat], opacityControl: true }],
      { onZoomTo: vi.fn() },
    )
    fireEvent.click(screen.getByLabelText('Hide layer'))
    // ⋮ still present (hover-revealed, not pinned).
    const trigger = screen.getByLabelText('Layer options')
    expect(trigger.className).not.toContain('active')
    fireEvent.click(trigger)
    // Show only/all still apply; Zoom + Opacity remove themselves.
    expect(screen.getByText('Show only this layer')).toBeTruthy()
    expect(screen.getByText('Show all layers')).toBeTruthy()
    expect(screen.queryByText('Zoom to layer')).toBeNull()
    expect(screen.queryByText('Opacity')).toBeNull()
  })
})

describe('hover-fade dirty pinning (.active)', () => {
  it('marks the visibility toggle active while the layer is hidden', () => {
    renderRow('f1', [{ id: 'a', name: 'A', variables: [cat] }])
    fireEvent.click(screen.getByLabelText('Hide layer'))
    expect(screen.getByLabelText('Show layer').className).toContain('active')
    fireEvent.click(screen.getByLabelText('Show layer'))
    expect(screen.getByLabelText('Hide layer').className).not.toContain(
      'active',
    )
  })

  it('marks the menu trigger active while its menu or the opacity bar is open', () => {
    renderRow('f2', [
      { id: 'a', name: 'A', variables: [cat], opacityControl: true },
    ])
    const trigger = screen.getByLabelText('Layer options')
    expect(trigger.className).not.toContain('active')
    fireEvent.click(trigger)
    expect(trigger.className).toContain('active') // menu open

    // Pick Opacity: the menu closes but the ⋮ stays pinned while the bar is
    // open.
    fireEvent.click(screen.getByRole('menuitem', { name: 'Opacity' }))
    expect(screen.queryByRole('menuitem')).toBeNull()
    expect(trigger.className).toContain('active')
  })
})

describe('<LegendOpacity> Item + Inline', () => {
  const layersWithOpacity: LegendLayerInput[] = [
    { id: 'a', name: 'A', variables: [cat], opacityControl: true },
  ]

  const openBar = () => {
    fireEvent.click(screen.getByLabelText('Layer options'))
    fireEvent.click(screen.getByRole('menuitem', { name: 'Opacity' }))
  }

  it('the menu item toggles the inline bar and stays selected while open', () => {
    renderRow('o1', layersWithOpacity)
    expect(screen.queryByRole('slider')).toBeNull()

    openBar()
    expect(screen.getByRole('slider', { name: 'Opacity' })).toBeTruthy()
    expect(screen.getByRole('textbox', { name: 'Opacity' })).toBeTruthy()

    // Reopen the menu: the item reflects the open bar and toggles it off.
    fireEvent.click(screen.getByLabelText('Layer options'))
    const item = screen.getByRole('menuitem', { name: 'Opacity' })
    expect(item.className).toContain('Mui-selected')
    fireEvent.click(item)
    expect(screen.queryByRole('slider')).toBeNull()
  })

  it('removes the item without opacityControl', () => {
    renderRow('o2', [{ id: 'a', name: 'A', variables: [cat] }])
    fireEvent.click(screen.getByLabelText('Layer options'))
    expect(screen.queryByRole('menuitem', { name: 'Opacity' })).toBeNull()
  })

  it('opening on a collapsed row auto-expands it; folding keeps the state', () => {
    renderRow('o3', layersWithOpacity)
    fireEvent.click(titleButton(/A/)) // collapse
    openBar()
    // Auto-expanded so the bar is visible.
    expect(getLegendStore('o3').getState().layers.a!.collapsed).toBe(false)
    expect(screen.getByRole('slider', { name: 'Opacity' })).toBeTruthy()

    // Folding hides the bar with the body but keeps opacityOpen: expanding
    // brings it back without touching the menu.
    fireEvent.click(titleButton(/A/))
    fireEvent.click(titleButton(/A/))
    expect(screen.getByRole('slider', { name: 'Opacity' })).toBeTruthy()
  })

  it('the ✕ closes the bar', () => {
    renderRow('o4', layersWithOpacity)
    openBar()
    fireEvent.click(screen.getByLabelText('Close'))
    expect(screen.queryByRole('slider')).toBeNull()
  })

  it('slider release commits setOpacity (0–1) to the store', () => {
    renderRow('c1', layersWithOpacity)
    openBar()
    const slider = screen.getByRole('slider', { name: 'Opacity' })
    // The hidden-input change path fires MUI's onChangeCommitted too.
    fireEvent.change(slider, { target: { value: '40' } })
    expect(getLegendStore('c1').getState().layers.a!.opacity).toBe(0.4)
  })

  it('percent input commits live and clamps to 0–100', () => {
    renderRow('c3', layersWithOpacity)
    openBar()
    const input = screen.getByRole('textbox', { name: 'Opacity' })

    fireEvent.change(input, { target: { value: '40' } })
    expect(getLegendStore('c3').getState().layers.a!.opacity).toBe(0.4)

    // Out-of-range input clamps (no ellipsized "1…" — the field shows 100).
    fireEvent.change(input, { target: { value: '250' } })
    expect(getLegendStore('c3').getState().layers.a!.opacity).toBe(1)
    expect((input as HTMLInputElement).value).toBe('100')

    // Empty input doesn't commit; blur snaps back to the store value.
    fireEvent.change(input, { target: { value: '' } })
    expect(getLegendStore('c3').getState().layers.a!.opacity).toBe(1)
    fireEvent.blur(input)
    expect((input as HTMLInputElement).value).toBe('100')
  })
})

describe('<LegendConfigSelect> dispatch', () => {
  it('a single section renders without its title; a pick writes the store', () => {
    renderRow('c2', [
      {
        id: 'a',
        name: 'A',
        variables: [cat],
        sections: [
          { id: 's1', title: 'Global', options: ['x', 'y'], active: 'x' },
        ],
      },
    ])
    fireEvent.click(screen.getByRole('button', { name: 'Attribute' }))
    expect(screen.queryByText('Global')).toBeNull() // single section → no title
    fireEvent.click(screen.getByRole('menuitem', { name: 'y' }))
    expect(getLegendStore('c2').getState().layers.a!.sections![0]!.active).toBe(
      'y',
    )
    // Menu closed after the pick.
    expect(screen.queryByRole('menuitem')).toBeNull()
  })

  it('multiple sections show titles and each keeps its own active', () => {
    renderRow('c4', [
      {
        id: 'a',
        name: 'A',
        variables: [cat],
        sections: [
          {
            id: 'global',
            title: 'Global',
            options: ['Preset 01', 'Preset 04'],
            active: 'Preset 04',
          },
          {
            id: 'vars',
            title: 'Variables 1',
            options: ['Atributo 01', 'Atributo 02'],
            active: 'Atributo 01',
          },
        ],
      },
    ])
    // Closed display = first section's active.
    const trigger = screen.getByRole('button', { name: 'Attribute' })
    expect(trigger.textContent).toBe('Preset 04')

    fireEvent.click(trigger)
    expect(screen.getByText('Global')).toBeTruthy()
    expect(screen.getByText('Variables 1')).toBeTruthy()
    // Both sections highlight their own active simultaneously.
    expect(
      screen.getByRole('menuitem', { name: 'Preset 04' }).className,
    ).toContain('Mui-selected')
    expect(
      screen.getByRole('menuitem', { name: 'Atributo 01' }).className,
    ).toContain('Mui-selected')

    // Picking in the second section leaves the first untouched.
    fireEvent.click(screen.getByRole('menuitem', { name: 'Atributo 02' }))
    const sections = getLegendStore('c4').getState().layers.a!.sections!
    expect(sections[0]!.active).toBe('Preset 04')
    expect(sections[1]!.active).toBe('Atributo 02')
    expect(trigger.textContent).toBe('Preset 04') // display unchanged
  })
})

describe('<LegendVisibilityToggle> controlled mode', () => {
  it('renders the eye for each tri-state and fires onToggle with the next visibility', () => {
    const onToggle = vi.fn()
    const { rerender } = render(
      <LegendProvider id='vt1' layers={[]}>
        <LegendVisibilityToggle visibility='visible' onToggle={onToggle} />
      </LegendProvider>,
    )
    // No Row/Group context needed — works standalone.
    const eye = screen.getByLabelText('Hide layer')
    expect(eye.className).not.toContain('active')
    fireEvent.click(eye)
    expect(onToggle).toHaveBeenLastCalledWith(false)

    rerender(
      <LegendProvider id='vt1' layers={[]}>
        <LegendVisibilityToggle visibility='mixed' onToggle={onToggle} />
      </LegendProvider>,
    )
    const mixed = screen.getByLabelText('Hide layer')
    expect(mixed.className).toContain('active')
    expect(
      mixed.querySelector('[data-testid="VisibilityIndeterminateIcon"]'),
    ).toBeTruthy()
    fireEvent.click(mixed)
    expect(onToggle).toHaveBeenLastCalledWith(false)

    rerender(
      <LegendProvider id='vt1' layers={[]}>
        <LegendVisibilityToggle visibility='hidden' onToggle={onToggle} />
      </LegendProvider>,
    )
    const hidden = screen.getByLabelText('Show layer')
    expect(hidden.className).toContain('active')
    fireEvent.click(hidden)
    expect(onToggle).toHaveBeenLastCalledWith(true)
  })

  it('controlled props win over the row context and accept label overrides', () => {
    const onToggle = vi.fn()
    renderRow('vt2', [{ id: 'a', name: 'A', variables: [cat] }])
    render(
      <LegendProvider
        id='vt3'
        layers={[{ id: 'a', name: 'A', variables: [cat] }]}
      >
        <LegendRow layerId='a'>
          <LegendActions>
            <LegendVisibilityToggle
              visibility='hidden'
              onToggle={onToggle}
              labels={{ hideLayer: 'Hide section', showLayer: 'Show section' }}
            />
          </LegendActions>
        </LegendRow>
      </LegendProvider>,
    )
    // The layer is visible in the store, but the controlled props render the
    // hidden eye with the custom label — context is ignored.
    const eye = screen.getByLabelText('Show section')
    fireEvent.click(eye)
    expect(onToggle).toHaveBeenCalledWith(true)
    expect(getLegendStore('vt3').getState().layers.a!.visible).toBe(true)
  })
})

describe('labels override', () => {
  it('Provider labels replace the defaults end-to-end', () => {
    render(
      <LegendProvider
        id='l10n'
        layers={[
          { id: 'a', name: 'A', variables: [cat], opacityControl: true },
        ]}
        labels={{ opacity: 'Opacidad', hideLayer: 'Ocultar capa' }}
      >
        <LegendRow layerId='a'>
          <LegendActions>
            <LegendVisibilityToggle />
            <LegendRowMenu>
              <LegendOpacity.Item />
            </LegendRowMenu>
          </LegendActions>
          <LegendOpacity.Inline />
          <LegendItem />
        </LegendRow>
      </LegendProvider>,
    )
    expect(screen.getByLabelText('Ocultar capa')).toBeTruthy()
    fireEvent.click(screen.getByLabelText('Layer options'))
    // The opacity menu item carries the overridden label…
    fireEvent.click(screen.getByRole('menuitem', { name: 'Opacidad' }))
    // …and so does the inline bar's slider.
    expect(screen.getByRole('slider', { name: 'Opacidad' })).toBeTruthy()
  })
})

describe('<LegendProvider> keepAlive', () => {
  it('preserves interactive state across remounts by default', () => {
    const ui = (
      <LegendProvider
        id='ka1'
        layers={[{ id: 'a', name: 'A', variables: [cat] }]}
      >
        <LegendRow layerId='a'>
          <LegendActions>
            <LegendVisibilityToggle />
          </LegendActions>
        </LegendRow>
      </LegendProvider>
    )
    const { unmount } = render(ui)
    fireEvent.click(screen.getByLabelText('Hide layer'))
    expect(getLegendStore('ka1').getState().layers.a!.visible).toBe(false)
    unmount()

    render(ui)
    // Store survived: still hidden after remount.
    expect(getLegendStore('ka1').getState().layers.a!.visible).toBe(false)
  })

  it('keepAlive=false resets state on remount', () => {
    const ui = (
      <LegendProvider
        id='ka2'
        keepAlive={false}
        layers={[{ id: 'a', name: 'A', variables: [cat] }]}
      >
        <LegendRow layerId='a'>
          <LegendActions>
            <LegendVisibilityToggle />
          </LegendActions>
        </LegendRow>
      </LegendProvider>
    )
    const { unmount } = render(ui)
    fireEvent.click(screen.getByLabelText('Hide layer'))
    unmount()

    render(ui)
    expect(getLegendStore('ka2').getState().layers.a!.visible).toBe(true)
  })
})
