import { assert, fixture, html, nextFrame } from '@open-wc/testing'
import UiButton from '../../../src/md/button/internals/button.js'
import { UiMock } from '../../helpers/UiMock.js'

import '../../../src/md/button/ui-button.js'
import '../../../src/md/icons/ui-icon.js'

describe('md', () => {
  describe('UiButton', () => {
    async function basicFixture(): Promise<UiButton> {
      return fixture(html`<ui-button color="filled">Label</ui-button>`)
    }

    async function formFixture(): Promise<HTMLFormElement> {
      return fixture(
        html`<form>
          <input name="text" value="abc" />
          <ui-button color="filled">Label</ui-button>
        </form>`
      )
    }

    async function submitFormFixture(): Promise<HTMLFormElement> {
      return fixture(
        html`<form>
          <input name="text" value="abc" />
          <ui-button color="filled" name="button" value="ok" type="submit">Label</ui-button>
        </form>`
      )
    }

    async function disabledFormFixture(): Promise<HTMLFormElement> {
      return fixture(
        html`<form>
          <input name="text" value="abc" />
          <ui-button color="filled" name="button" value="ok" type="submit" disabled>Label</ui-button>
        </form>`
      )
    }

    async function disabledFixture(): Promise<UiButton> {
      return fixture(html`<ui-button color="filled" disabled>Label</ui-button>`)
    }

    async function tabindexFixture(): Promise<UiButton> {
      return fixture(html`<ui-button color="filled" tabindex="1">Label</ui-button>`)
    }

    async function iconFixture(): Promise<UiButton> {
      return fixture(
        html`<ui-button color="filled">
          <ui-icon slot="icon" icon="add"></ui-icon>
          Label
        </ui-button>`
      )
    }

    async function trailingIconFixture(): Promise<UiButton> {
      return fixture(
        html`<ui-button color="filled" trailingIcon>
          <ui-icon slot="icon" icon="add"></ui-icon>
          Label
        </ui-button>`
      )
    }

    async function toggleFixture(): Promise<UiButton> {
      return fixture(
        html`<ui-button color="filled" toggle>
          <ui-icon slot="icon" icon="add"></ui-icon>
          Label
        </ui-button>`
      )
    }

    async function toggleSelectedFixture(): Promise<UiButton> {
      return fixture(
        html`<ui-button color="filled" toggle selected>
          <ui-icon slot="icon" icon="add"></ui-icon>
          Label
        </ui-button>`
      )
    }

    describe('Form button', () => {
      it('has formAssociated set', async () => {
        assert.isTrue(UiButton.formAssociated)
      })

      it('has no form when not in form', async () => {
        const button = await basicFixture()
        assert.isNull(button.form)
      })

      it('has form when in a form', async () => {
        const form = await formFixture()
        const button = form.querySelector('ui-button')!
        assert.ok(button.form, 'has a form')
        assert.isTrue(button.form === form, 'has the parent form')
      })

      it('does not trigger submit when not "submit"', async () => {
        const form = await formFixture()
        const button = form.querySelector('ui-button')!
        let event: Event | undefined
        form.addEventListener('submit', (e: SubmitEvent) => {
          e.preventDefault()
          event = e
        })
        button.click()
        assert.notOk(event)
      })

      it('removes the native "button" after submitting', async () => {
        const form = await submitFormFixture()
        const button = form.querySelector('ui-button')!
        form.addEventListener('submit', (e: SubmitEvent) => {
          e.preventDefault()
        })
        button.click()
        const nativeButton = form.querySelector('button')
        assert.notOk(nativeButton)
      })

      it('registers the form values', async () => {
        const form = await submitFormFixture()
        const button = form.querySelector('ui-button')!
        assert.isTrue(form.elements[1] === button, 'the form has the button')
      })

      it('submits the form when clicking on the button', async () => {
        const form = await submitFormFixture()
        const button = form.querySelector('ui-button')!
        let event: SubmitEvent | undefined
        form.addEventListener('submit', (e: SubmitEvent) => {
          e.preventDefault()
          event = e
        })
        button.click()
        assert.ok(event, 'has the event')
        const submitter = event!.submitter as UiButton
        assert.equal(submitter.value, 'ok', 'submitter has the value')
        assert.equal(submitter.name, 'button', 'submitter has the name')
      })

      it('submits the form with Space bar', async () => {
        const form = await submitFormFixture()
        const button = form.querySelector('ui-button')!
        let event: SubmitEvent | undefined
        form.addEventListener('submit', (e: SubmitEvent) => {
          e.preventDefault()
          event = e
        })
        await UiMock.keyPress(button, 'Space')
        assert.ok(event)
      })

      it('submits the form with Enter', async () => {
        const form = await submitFormFixture()
        const button = form.querySelector('ui-button')!
        let event: SubmitEvent | undefined
        form.addEventListener('submit', (e: SubmitEvent) => {
          e.preventDefault()
          event = e
        })
        await UiMock.keyPress(button, 'Enter')
        assert.ok(event)
      })
    })

    describe('Disabled button', () => {
      it('is not disabled by default', async () => {
        const button = await basicFixture()
        assert.isFalse(button.disabled)
      })

      it('has no aria-disabled by default', async () => {
        const button = await basicFixture()
        assert.isFalse(button.hasAttribute('aria-disabled'))
      })

      it('is is disabled via the markup', async () => {
        const button = await disabledFixture()
        assert.isTrue(button.disabled)
      })

      it('removes the tabindex', async () => {
        const button = await disabledFixture()
        assert.isFalse(button.hasAttribute('tabindex'))
      })

      it('sets the aria-disabled attribute', async () => {
        const button = await disabledFixture()
        assert.equal(button.getAttribute('aria-disabled'), 'true')
      })

      it('disables the button via property setter', async () => {
        const button = await basicFixture()
        button.disabled = true
        assert.equal(button.getAttribute('aria-disabled'), 'true')
        assert.isFalse(button.hasAttribute('tabindex'))
      })

      it('restores the button from the disabled state', async () => {
        const button = await tabindexFixture()
        button.disabled = true
        await nextFrame()
        button.disabled = false
        await nextFrame()
        assert.equal(button.getAttribute('tabindex'), '1')
        assert.isFalse(button.hasAttribute('aria-disabled'))
      })

      it('does not trigger submit when disabled', async () => {
        const form = await disabledFormFixture()
        const button = form.querySelector('ui-button')!
        let event: Event | undefined
        form.addEventListener('submit', (e: SubmitEvent) => {
          e.preventDefault()
          event = e
        })
        button.click()
        assert.notOk(event)
      })
    })

    describe('Icon placement', () => {
      it('places the icon as a prefix by default', async () => {
        const button = await iconFixture()
        const content = button.shadowRoot!
        assert.equal(content?.children[2].localName, 'slot')
      })

      it('places the icon as a suffix', async () => {
        const button = await trailingIconFixture()
        const content = button.shadowRoot!
        assert.equal(content?.children[3].localName, 'slot')
      })
    })

    describe('Ripple effect', () => {
      it('renders the ripple element', async () => {
        const button = await basicFixture()
        const ripple = button.shadowRoot!.querySelector('ui-ripple')
        assert.ok(ripple)
      })
    })

    describe('a11y', () => {
      it('is accessible when in a default state', async () => {
        const button = await basicFixture()
        await assert.isAccessible(button)
      })

      it('is accessible when in the disabled state', async () => {
        const button = await disabledFixture()
        await assert.isAccessible(button)
      })

      it('sets the role', async () => {
        const button = await basicFixture()
        assert.equal(button.getAttribute('role'), 'button')
      })

      it('sets the tabindex', async () => {
        const button = await basicFixture()
        assert.equal(button.getAttribute('tabindex'), '0')
      })

      it('respects the existing tabindex', async () => {
        const button = await tabindexFixture()
        assert.equal(button.getAttribute('tabindex'), '1')
      })

      it('is accessible when not selected', async () => {
        const button = await toggleFixture()
        await assert.isAccessible(button)
      })

      it('is accessible when selected', async () => {
        const button = await toggleSelectedFixture()
        await assert.isAccessible(button)
      })
    })

    describe('Value and Name', () => {
      it('sets the value', async () => {
        const button = await basicFixture()
        button.value = 'test'
        assert.equal(button.value, 'test')
      })

      it('sets the name', async () => {
        const button = await basicFixture()
        button.name = 'test'
        assert.equal(button.name, 'test')
      })

      it('sets the form value', async () => {
        const button = await basicFixture()
        button.value = 'test'
        await nextFrame()
        assert.equal(button.value, 'test')
      })
    })

    describe('Type', () => {
      it('sets the default type', async () => {
        const button = await basicFixture()
        assert.equal(button.type, 'button')
      })

      it('sets the type', async () => {
        const button = await basicFixture()
        button.type = 'submit'
        assert.equal(button.type, 'submit')
      })
    })

    describe('Internals', () => {
      it('has validity', async () => {
        const button = await basicFixture()
        assert.ok(button.validity)
      })

      it('has empty validationMessage', async () => {
        const button = await basicFixture()
        assert.equal(button.validationMessage, '')
      })

      it('has willValidate', async () => {
        const button = await basicFixture()
        assert.ok(button.willValidate)
      })

      it('calls checkValidity', async () => {
        const button = await basicFixture()
        assert.isTrue(button.checkValidity())
      })

      it('calls reportValidity', async () => {
        const button = await basicFixture()
        assert.isTrue(button.reportValidity())
      })
    })
  })
})
