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

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

describe('md', () => {
  describe('Dialog', () => {
    async function basicFixture(): Promise<UiDialog> {
      return fixture(html` <ui-dialog> Content </ui-dialog>`)
    }

    async function modalFixture(): Promise<UiDialog> {
      return fixture(html` <ui-dialog modal> Content </ui-dialog>`)
    }

    async function iconFixture(): Promise<UiDialog> {
      return fixture(
        html` <ui-dialog>
          <ui-icon slot="icon" icon="deleteOutline"></ui-icon>
          Content
        </ui-dialog>`
      )
    }

    async function titleFixture(): Promise<UiDialog> {
      return fixture(
        html` <ui-dialog>
          <span slot="title">The title</span>
          Content
        </ui-dialog>`
      )
    }

    async function buttonFixture(): Promise<UiDialog> {
      return fixture(
        html` <ui-dialog>
          Content
          <ui-button color="text" slot="button">Learn more</ui-button>
          <ui-button color="text" slot="button" value="dismiss">Cancel</ui-button>
          <ui-button color="text" slot="button" value="confirm">Accept</ui-button>
        </ui-dialog>`
      )
    }

    describe('open/close', () => {
      it('opens the native dialog via the "open" attribute', async () => {
        const element = await basicFixture()
        assert.isFalse(element.dialog.open, 'native dialog is closed initially')

        element.open = true
        await element.updateComplete
        assert.isTrue(element.dialog.open, 'native dialog is opened')

        element.open = false
        await element.updateComplete
        assert.isFalse(element.dialog.open, 'native dialog is closed again')
      })

      it('closes the dialog via the Escape button', async () => {
        const element = await buttonFixture()
        // the dialog requires a focusable element
        const button = element.querySelector('ui-button')!
        button.focus()
        await UiMock.keyPress(element, 'Escape', { key: 'Escape' })
        assert.equal(element.open, false)
      })

      it('closes the dialog via the dismiss slotted button', async () => {
        const element = await buttonFixture()
        const button = element.querySelector('ui-button[value="dismiss"]') as UiButton
        button.click()
        assert.equal(element.open, false)
      })

      it('closes the dialog via the confirm slotted button', async () => {
        const element = await buttonFixture()
        const button = element.querySelector('ui-button[value="confirm"]') as UiButton
        button.click()
        assert.equal(element.open, false)
      })

      it('closes the dialog via the dismissLabel button', async () => {
        const element = await basicFixture()
        element.dismissLabel = 'Close'
        await element.updateComplete
        const button = element.shadowRoot!.querySelector('.internal-button[value="dismiss"]') as UiButton
        button.click()
        assert.equal(element.open, false)
      })

      it('closes the dialog via the confirmLabel button', async () => {
        const element = await basicFixture()
        element.confirmLabel = 'Close'
        await element.updateComplete
        const button = element.shadowRoot!.querySelector('.internal-button[value="confirm"]') as UiButton
        button.click()
        assert.equal(element.open, false)
      })

      it('dispatches the close event with cancelled = true', async () => {
        const element = await buttonFixture()
        const spy = sinon.spy()
        element.addEventListener('close', spy)
        const button = element.querySelector('ui-button[value="dismiss"]') as UiButton
        button.click()
        assert.isTrue(spy.calledOnce, 'the event was dispatched')
        const event = spy.args[0][0] as CustomEvent<UiDialogClosingReason>
        assert.isTrue(event.detail.cancelled, 'the cancelled flag is set')
      })

      it('dispatches the close event with cancelled = true', async () => {
        const element = await buttonFixture()
        const spy = sinon.spy()
        element.addEventListener('close', spy)
        const button = element.querySelector('ui-button[value="confirm"]') as UiButton
        button.click()
        assert.isTrue(spy.calledOnce, 'the event was dispatched')
        const event = spy.args[0][0] as CustomEvent<UiDialogClosingReason>
        assert.isFalse(event.detail.cancelled, 'the cancelled flag is not set')
      })

      it('dispatches the dialogValue', async () => {
        const element = await buttonFixture()
        element.dialogValue = 'test'
        const spy = sinon.spy()
        element.addEventListener('close', spy)
        const button = element.querySelector('ui-button[value="confirm"]') as UiButton
        button.click()
        assert.isTrue(spy.calledOnce, 'the event was dispatched')
        const event = spy.args[0][0] as CustomEvent<UiDialogClosingReason>
        assert.equal(event.detail.value, 'test', 'has the value')
      })
    })

    describe('modal dialog', () => {
      it('opens the dialog as modal', async () => {
        const element = await modalFixture()
        const spy = sinon.spy(element.dialog, 'showModal')
        element.open = true
        await element.updateComplete
        assert.isTrue(spy.calledOnce)
      })

      it('applies modal class to dialog when modal is true', async () => {
        const element = await basicFixture()
        element.modal = true
        await element.updateComplete

        const dialog = element.shadowRoot!.querySelector('dialog') as HTMLDialogElement
        assert.ok(dialog, 'has the dialog element')
        assert.isTrue(dialog.classList.contains('modal'), 'has modal class')
      })

      it('applies non-modal class to dialog when modal is false', async () => {
        const element = await basicFixture()
        element.modal = false
        await element.updateComplete

        const dialog = element.shadowRoot!.querySelector('dialog') as HTMLDialogElement
        assert.ok(dialog, 'has the dialog element')
        assert.isTrue(dialog.classList.contains('non-modal'), 'has non-modal class')
      })
    })

    describe('icon', () => {
      it('renders the icon slot', async () => {
        const element = await basicFixture()
        const slot = element.shadowRoot!.querySelector('slot[name="icon"]') as HTMLSlotElement
        assert.ok(slot, 'has the slot')
      })

      it('does not render padding around the icon slot', async () => {
        const element = await basicFixture()
        const container = element.shadowRoot!.querySelector('.icon') as HTMLElement
        assert.isFalse(container.classList.contains('with-icon'), 'has no with-icon class')
      })

      it('renders padding around the icon slot with an icon', async () => {
        const element = await iconFixture()
        const container = element.shadowRoot!.querySelector('.icon') as HTMLElement
        assert.isTrue(container.classList.contains('with-icon'), 'has the with-icon class')
      })
    })

    describe('title', () => {
      it('renders the title slot', async () => {
        const element = await basicFixture()
        const slot = element.shadowRoot!.querySelector('slot[name="title"]') as HTMLSlotElement
        assert.ok(slot, 'has the slot')
      })

      it('does not render padding around the title slot', async () => {
        const element = await basicFixture()
        const container = element.shadowRoot!.querySelector('.title') as HTMLElement
        assert.isFalse(container.classList.contains('with-title'), 'has no with-title class')
      })

      it('renders padding around the title slot with a title', async () => {
        const element = await titleFixture()
        const container = element.shadowRoot!.querySelector('.title') as HTMLElement
        assert.isTrue(container.classList.contains('with-title'), 'has the with-title class')
      })
    })

    describe('buttons', () => {
      it('renders the button slot', async () => {
        const element = await basicFixture()
        const slot = element.shadowRoot!.querySelector('slot[name="button"]') as HTMLSlotElement
        assert.ok(slot, 'has the slot')
      })

      it('does not render padding around the buttons slot', async () => {
        const element = await basicFixture()
        const container = element.shadowRoot!.querySelector('.buttons') as HTMLElement
        assert.isFalse(container.classList.contains('with-buttons'), 'has no with-buttons class')
      })

      it('renders the confirm button', async () => {
        const element = await basicFixture()
        element.confirmLabel = 'OK label'
        await element.updateComplete
        const container = element.shadowRoot!.querySelector('.buttons') as HTMLElement
        assert.isTrue(container.classList.contains('with-buttons'), 'has the with-buttons class')
        const button = element.shadowRoot!.querySelector('.internal-button') as UiButton
        assert.ok(button, 'has the button')
        assert.equal(button.value, 'confirm')
        assert.equal(button.textContent!.trim(), 'OK label')
      })

      it('renders the dismiss button', async () => {
        const element = await basicFixture()
        element.dismissLabel = 'Cancel label'
        await element.updateComplete
        const container = element.shadowRoot!.querySelector('.buttons') as HTMLElement
        assert.isTrue(container.classList.contains('with-buttons'), 'has the with-buttons class')
        const button = element.shadowRoot!.querySelector('.internal-button') as UiButton
        assert.ok(button, 'has the button')
        assert.equal(button.value, 'dismiss')
        assert.equal(button.textContent!.trim(), 'Cancel label')
      })

      it('renders the slotted buttons', async () => {
        const element = await buttonFixture()
        const container = element.shadowRoot!.querySelector('.buttons') as HTMLElement
        assert.isTrue(container.classList.contains('with-buttons'), 'has the with-buttons class')
      })

      it('applies destructive class to confirm button when destructive is true', async () => {
        const element = await basicFixture()
        element.confirmLabel = 'Delete'
        element.destructive = true
        await element.updateComplete

        const button = element.shadowRoot!.querySelector('.internal-button[value="confirm"]') as UiButton
        assert.ok(button, 'has the confirm button')
        assert.isTrue(button.classList.contains('destructive'), 'has destructive class')
      })

      it('does not apply destructive class when destructive is false', async () => {
        const element = await basicFixture()
        element.confirmLabel = 'Confirm'
        element.destructive = false
        await element.updateComplete

        const button = element.shadowRoot!.querySelector('.internal-button[value="confirm"]') as UiButton
        assert.ok(button, 'has the confirm button')
        assert.isFalse(button.classList.contains('destructive'), 'does not have destructive class')
      })
    })

    describe('form handling', () => {
      async function formWrappedDialogFixture(): Promise<{ form: HTMLFormElement; dialog: UiDialog }> {
        const container = await fixture(html`
          <form>
            <ui-dialog submitClose>
              <span slot="title">Form Dialog</span>
              <input type="text" name="username" required />
              <ui-button color="text" slot="button" value="dismiss">Cancel</ui-button>
              <ui-button color="text" slot="button" value="confirm" type="submit">Submit</ui-button>
            </ui-dialog>
          </form>
        `)
        const form = container as HTMLFormElement
        const dialog = form.querySelector('ui-dialog') as UiDialog
        return { form, dialog }
      }

      async function formWithoutSubmitCloseFixture(): Promise<{ form: HTMLFormElement; dialog: UiDialog }> {
        const container = await fixture(html`
          <form>
            <ui-dialog>
              <span slot="title">Form Dialog</span>
              <input type="text" name="username" required />
              <ui-button color="text" slot="button" value="dismiss">Cancel</ui-button>
              <ui-button color="text" slot="button" value="confirm" type="submit">Submit</ui-button>
            </ui-dialog>
          </form>
        `)
        const form = container as HTMLFormElement
        const dialog = form.querySelector('ui-dialog') as UiDialog
        return { form, dialog }
      }

      describe('dialog wrapped in form', () => {
        it('should detect parent form when connected', async () => {
          const { dialog } = await formWrappedDialogFixture()
          await dialog.updateComplete

          // The form should be detected during connectedCallback
          // We can't directly access private fields, so we test the behavior instead
          assert.ok(dialog.submitClose, 'dialog should be configured for form handling')
        })

        it('should close dialog when form is submitted and submitClose is true', async () => {
          const { form, dialog } = await formWrappedDialogFixture()
          dialog.open = true
          await dialog.updateComplete

          const spy = sinon.spy()
          dialog.addEventListener('close', spy)

          // Simulate form submission
          const submitEvent = new SubmitEvent('submit', { bubbles: true, cancelable: true })
          form.dispatchEvent(submitEvent)

          assert.isFalse(dialog.open, 'dialog should be closed')
          assert.isTrue(spy.calledOnce, 'close event should be dispatched')
          const event = spy.args[0][0] as CustomEvent
          assert.isFalse(event.detail.cancelled, 'dialog should be confirmed, not cancelled')
        })

        it('should not close dialog when form is submitted and submitClose is false', async () => {
          const { form, dialog } = await formWithoutSubmitCloseFixture()
          dialog.open = true
          await dialog.updateComplete

          const spy = sinon.spy()
          dialog.addEventListener('close', spy)

          // Simulate form submission
          const submitEvent = new SubmitEvent('submit', { bubbles: true, cancelable: true })
          form.dispatchEvent(submitEvent)

          assert.isTrue(dialog.open, 'dialog should remain open')
          assert.isFalse(spy.called, 'close event should not be dispatched')
        })

        it('should not handle form submit when submit button is clicked directly', async () => {
          const { dialog } = await formWrappedDialogFixture()
          dialog.open = true
          await dialog.updateComplete

          const submitButton = dialog.querySelector('ui-button[type="submit"]') as UiButton
          const spy = sinon.spy()
          dialog.addEventListener('close', spy)

          // Click the submit button - this should not close the dialog immediately
          // because we yield control to the form
          submitButton.click()

          // The dialog should still be open because the form hasn't been submitted yet
          assert.isTrue(dialog.open, 'dialog should remain open when submit button is clicked')
        })

        it('should remove form event listener when disconnected', async () => {
          const { form, dialog } = await formWrappedDialogFixture()
          const removeEventListenerSpy = sinon.spy(form, 'removeEventListener')

          dialog.remove()

          // We can't test the private method directly, but we can verify the spy was called
          // The actual method name is not accessible, so we test the behavior instead
          assert.isTrue(removeEventListenerSpy.called, 'removeEventListener should be called on form')
        })
      })

      describe('edge cases', () => {
        it('should handle form submission when dialog is not open', async () => {
          const { form, dialog } = await formWrappedDialogFixture()
          // Dialog is closed by default
          assert.isFalse(dialog.open, 'dialog should be closed initially')

          const spy = sinon.spy()
          dialog.addEventListener('close', spy)

          // Submit form when dialog is closed
          const submitEvent = new SubmitEvent('submit', { bubbles: true, cancelable: true })
          form.dispatchEvent(submitEvent)

          // Should still close the dialog (set open to false even though it's already false)
          assert.isFalse(dialog.open, 'dialog should remain closed')
          assert.isTrue(spy.calledOnce, 'close event should still be dispatched')
        })

        it('should handle multiple form submissions', async () => {
          const { form, dialog } = await formWrappedDialogFixture()
          dialog.open = true
          await dialog.updateComplete

          const spy = sinon.spy()
          dialog.addEventListener('close', spy)

          // Submit form multiple times
          const submitEvent1 = new SubmitEvent('submit', { bubbles: true, cancelable: true })
          const submitEvent2 = new SubmitEvent('submit', { bubbles: true, cancelable: true })

          form.dispatchEvent(submitEvent1)
          form.dispatchEvent(submitEvent2)

          assert.equal(spy.callCount, 2, 'close event should be dispatched for each submission')
        })

        it('should maintain form reference across re-connections', async () => {
          const { form, dialog } = await formWrappedDialogFixture()

          // Remove and re-add dialog
          const parent = form
          dialog.remove()
          await dialog.updateComplete

          parent.appendChild(dialog)
          await dialog.updateComplete

          // Test the behavior instead of accessing private fields
          // If form handling is working, submitClose should still work
          dialog.open = true
          await dialog.updateComplete

          const spy = sinon.spy()
          dialog.addEventListener('close', spy)

          const submitEvent = new SubmitEvent('submit', { bubbles: true, cancelable: true })
          form.dispatchEvent(submitEvent)

          assert.isTrue(spy.called, 'form handling should work after reconnection')
        })
      })
    })
  })
})
