import React, { useState } from 'react'
import { render } from '@testing-library/react'

import { Switch } from './switch'
import {
  SwitchState,
  assertSwitch,
  getSwitch,
  assertActiveElement,
  getSwitchLabel,
} from '../../test-utils/accessibility-assertions'
import { press, click, Keys } from '../../test-utils/interactions'

jest.mock('../../hooks/use-id')

describe('Safe guards', () => {
  it('should be possible to render a Switch without crashing', () => {
    render(<Switch checked={false} onChange={console.log} />)
  })
})

describe('Rendering', () => {
  it('should be possible to render an (on) Switch using a render prop', () => {
    render(
      <Switch checked={true} onChange={console.log}>
        {({ checked }) => <span>{checked ? 'On' : 'Off'}</span>}
      </Switch>
    )

    assertSwitch({ state: SwitchState.On, textContent: 'On' })
  })

  it('should be possible to render an (off) Switch using a render prop', () => {
    render(
      <Switch checked={false} onChange={console.log}>
        {({ checked }) => <span>{checked ? 'On' : 'Off'}</span>}
      </Switch>
    )

    assertSwitch({ state: SwitchState.Off, textContent: 'Off' })
  })

  it('should be possible to render an (on) Switch using an `as` prop', () => {
    render(<Switch as="span" checked={true} onChange={console.log} />)
    assertSwitch({ state: SwitchState.On, tag: 'span' })
  })

  it('should be possible to render an (off) Switch using an `as` prop', () => {
    render(<Switch as="span" checked={false} onChange={console.log} />)
    assertSwitch({ state: SwitchState.Off, tag: 'span' })
  })

  it('should be possible to use the switch contents as the label', () => {
    render(
      <Switch checked={false} onChange={console.log}>
        <span>Enable notifications</span>
      </Switch>
    )
    assertSwitch({ state: SwitchState.Off, label: 'Enable notifications' })
  })
})

describe('Render composition', () => {
  it('should be possible to render a Switch.Group, Switch and Switch.Label', () => {
    render(
      <Switch.Group>
        <Switch checked={false} onChange={console.log} />
        <Switch.Label>Enable notifications</Switch.Label>
      </Switch.Group>
    )

    assertSwitch({ state: SwitchState.Off, label: 'Enable notifications' })
  })

  it('should be possible to render a Switch.Group, Switch and Switch.Label (before the Switch)', () => {
    render(
      <Switch.Group>
        <Switch.Label>Label B</Switch.Label>
        <Switch checked={false} onChange={console.log}>
          Label A
        </Switch>
      </Switch.Group>
    )

    // Warning! Using aria-label or aria-labelledby will hide any descendant content from assistive
    // technologies.
    //
    // Thus: Label A should not be part of the "label" in this case
    assertSwitch({ state: SwitchState.Off, label: 'Label B' })
  })

  it('should be possible to render a Switch.Group, Switch and Switch.Label (after the Switch)', () => {
    render(
      <Switch.Group>
        <Switch checked={false} onChange={console.log}>
          Label A
        </Switch>
        <Switch.Label>Label B</Switch.Label>
      </Switch.Group>
    )

    // Warning! Using aria-label or aria-labelledby will hide any descendant content from assistive
    // technologies.
    //
    // Thus: Label A should not be part of the "label" in this case
    assertSwitch({ state: SwitchState.Off, label: 'Label B' })
  })

  it('should be possible to render a Switch.Group, Switch and Switch.Description (before the Switch)', async () => {
    render(
      <Switch.Group>
        <Switch.Description>This is an important feature</Switch.Description>
        <Switch checked={false} onChange={console.log} />
      </Switch.Group>
    )

    assertSwitch({ state: SwitchState.Off, description: 'This is an important feature' })
  })

  it('should be possible to render a Switch.Group, Switch and Switch.Description (after the Switch)', () => {
    render(
      <Switch.Group>
        <Switch checked={false} onChange={console.log} />
        <Switch.Description>This is an important feature</Switch.Description>
      </Switch.Group>
    )

    assertSwitch({ state: SwitchState.Off, description: 'This is an important feature' })
  })

  it('should be possible to render a Switch.Group, Switch, Switch.Label and Switch.Description', () => {
    render(
      <Switch.Group>
        <Switch.Label>Label A</Switch.Label>
        <Switch checked={false} onChange={console.log} />
        <Switch.Description>This is an important feature</Switch.Description>
      </Switch.Group>
    )

    assertSwitch({
      state: SwitchState.Off,
      label: 'Label A',
      description: 'This is an important feature',
    })
  })
})

describe('Keyboard interactions', () => {
  describe('`Space` key', () => {
    it('should be possible to toggle the Switch with Space', async () => {
      let handleChange = jest.fn()
      function Example() {
        let [state, setState] = useState(false)
        return (
          <Switch
            checked={state}
            onChange={value => {
              setState(value)
              handleChange(value)
            }}
          />
        )
      }

      render(<Example />)

      // Ensure checkbox is off
      assertSwitch({ state: SwitchState.Off })

      // Focus the switch
      getSwitch()?.focus()

      // Toggle
      await press(Keys.Space)

      // Ensure state is on
      assertSwitch({ state: SwitchState.On })

      // Toggle
      await press(Keys.Space)

      // Ensure state is off
      assertSwitch({ state: SwitchState.Off })
    })
  })

  describe('`Enter` key', () => {
    it('should not be possible to use Enter to toggle the Switch', async () => {
      let handleChange = jest.fn()
      render(<Switch checked={false} onChange={handleChange} />)

      // Ensure checkbox is off
      assertSwitch({ state: SwitchState.Off })

      // Focus the switch
      getSwitch()?.focus()

      // Try to toggle
      await press(Keys.Enter)

      expect(handleChange).not.toHaveBeenCalled()
    })
  })

  describe('`Tab` key', () => {
    it('should be possible to tab away from the Switch', async () => {
      render(
        <div>
          <Switch checked={false} onChange={console.log} />
          <button id="btn">Other element</button>
        </div>
      )

      // Ensure checkbox is off
      assertSwitch({ state: SwitchState.Off })

      // Focus the switch
      getSwitch()?.focus()

      // Expect the switch to be active
      assertActiveElement(getSwitch())

      // Toggle
      await press(Keys.Tab)

      // Expect the button to be active
      assertActiveElement(document.getElementById('btn'))
    })
  })
})

describe('Mouse interactions', () => {
  it('should be possible to toggle the Switch with a click', async () => {
    let handleChange = jest.fn()
    function Example() {
      let [state, setState] = useState(false)
      return (
        <Switch
          checked={state}
          onChange={value => {
            setState(value)
            handleChange(value)
          }}
        />
      )
    }

    render(<Example />)

    // Ensure checkbox is off
    assertSwitch({ state: SwitchState.Off })

    // Toggle
    await click(getSwitch())

    // Ensure state is on
    assertSwitch({ state: SwitchState.On })

    // Toggle
    await click(getSwitch())

    // Ensure state is off
    assertSwitch({ state: SwitchState.Off })
  })

  it('should be possible to toggle the Switch with a click on the Label', async () => {
    let handleChange = jest.fn()
    function Example() {
      let [state, setState] = useState(false)
      return (
        <Switch.Group>
          <Switch
            checked={state}
            onChange={value => {
              setState(value)
              handleChange(value)
            }}
          />
          <Switch.Label>The label</Switch.Label>
        </Switch.Group>
      )
    }

    render(<Example />)

    // Ensure checkbox is off
    assertSwitch({ state: SwitchState.Off })

    // Toggle
    await click(getSwitchLabel())

    // Ensure the switch is focused
    assertActiveElement(getSwitch())

    // Ensure state is on
    assertSwitch({ state: SwitchState.On })

    // Toggle
    await click(getSwitchLabel())

    // Ensure the switch is focused
    assertActiveElement(getSwitch())

    // Ensure state is off
    assertSwitch({ state: SwitchState.Off })
  })

  it('should not be possible to toggle the Switch with a click on the Label (passive)', async () => {
    let handleChange = jest.fn()
    function Example() {
      let [state, setState] = useState(false)
      return (
        <Switch.Group>
          <Switch
            checked={state}
            onChange={value => {
              setState(value)
              handleChange(value)
            }}
          />
          <Switch.Label passive>The label</Switch.Label>
        </Switch.Group>
      )
    }

    render(<Example />)

    // Ensure checkbox is off
    assertSwitch({ state: SwitchState.Off })

    // Toggle
    await click(getSwitchLabel())

    // Ensure state is still off
    assertSwitch({ state: SwitchState.Off })
  })
})
