import { assert, fixture, html, nextFrame, oneEvent } from '@open-wc/testing'
import sinon from 'sinon'
import UiMenuItem from '../../../src/md/menu/internal/MenuItem.js'
import UiSubMenu from '../../../src/md/menu/internal/SubMenu.js'

import '../../../src/md/menu/ui-menu.js'
import '../../../src/md/menu/ui-menu-item.js'
import '../../../src/md/menu/ui-sub-menu.js'
import '../../../src/md/icons/ui-icon.js'

describe('md', () => {
  describe('MenuItem', () => {
    async function basicFixture(): Promise<UiMenuItem> {
      return fixture(html`<ui-menu-item>Test Item</ui-menu-item>`)
    }

    async function withSubmenuFixture(): Promise<HTMLElement> {
      return fixture(html`
        <div>
          <ui-menu-item id="parent-item" submenu="test-submenu">
            <span slot="start"><ui-icon>folder</ui-icon></span>
            <span>Parent Item</span>
          </ui-menu-item>
          <ui-sub-menu id="test-submenu" anchor="parent-item">
            <ui-menu-item>Child Item 1</ui-menu-item>
            <ui-menu-item>Child Item 2</ui-menu-item>
          </ui-sub-menu>
        </div>
      `)
    }

    async function disabledFixture(): Promise<UiMenuItem> {
      return fixture(html`<ui-menu-item disabled>Disabled Item</ui-menu-item>`)
    }

    async function withSlotsFixture(): Promise<UiMenuItem> {
      return fixture(html`
        <ui-menu-item>
          <span slot="start"><ui-icon>add</ui-icon></span>
          <span>Main Content</span>
          <span slot="end"><ui-icon>arrow_right</ui-icon></span>
          <span slot="end-text">Ctrl+N</span>
        </ui-menu-item>
      `)
    }

    describe('Basic functionality', () => {
      it('should create menu item element', async () => {
        const element = await basicFixture()
        assert.instanceOf(element, UiMenuItem)
        assert.equal(element.tagName.toLowerCase(), 'ui-menu-item')
      })

      it('should set correct ARIA attributes', async () => {
        const element = await basicFixture()
        assert.equal(element.getAttribute('role'), 'menuitem')
      })

      it('should generate ID if not present', async () => {
        const element = await basicFixture()
        assert.isString(element.id)
        assert.isTrue(element.id.length > 0)
      })

      it('should preserve existing ID', async () => {
        const element: UiMenuItem = await fixture(html`<ui-menu-item id="custom-id">Test</ui-menu-item>`)
        assert.equal(element.id, 'custom-id')
      })
    })

    describe('Submenu functionality', () => {
      it('should detect submenu presence', async () => {
        const container = await withSubmenuFixture()
        const menuItem = container.querySelector('#parent-item') as UiMenuItem
        await nextFrame()

        assert.isTrue(menuItem.hasSubMenu)
        assert.equal(menuItem.submenu, 'test-submenu')
      })

      it('should get submenu element reference', async () => {
        const container = await withSubmenuFixture()
        const menuItem = container.querySelector('#parent-item') as UiMenuItem
        const submenu = container.querySelector('#test-submenu') as UiSubMenu
        await nextFrame()

        assert.equal(menuItem.subMenuElement, submenu)
      })

      it('should open submenu', async () => {
        const container = await withSubmenuFixture()
        const menuItem = container.querySelector('#parent-item') as UiMenuItem
        const submenu = container.querySelector('#test-submenu') as UiSubMenu
        const showSpy = sinon.spy(submenu, 'show')
        await nextFrame()

        menuItem.openSubMenu()
        await nextFrame()

        assert.isTrue(showSpy.calledOnce)
        assert.equal(menuItem.getAttribute('aria-expanded'), 'true')
      })

      it('should close submenu', async () => {
        const container = await withSubmenuFixture()
        const menuItem = container.querySelector('#parent-item') as UiMenuItem
        const submenu = container.querySelector('#test-submenu') as UiSubMenu
        const hideSpy = sinon.spy(submenu, 'hide')
        await nextFrame()

        menuItem.openSubMenu()
        await nextFrame()
        menuItem.closeSubMenu()
        await nextFrame()

        assert.isTrue(hideSpy.calledOnce)
        assert.equal(menuItem.getAttribute('aria-expanded'), 'false')
      })

      it('should toggle submenu', async () => {
        const container = await withSubmenuFixture()
        const menuItem = container.querySelector('#parent-item') as UiMenuItem
        const submenu = container.querySelector('#test-submenu') as UiSubMenu
        const showSpy = sinon.spy(submenu, 'show')
        const hideSpy = sinon.spy(submenu, 'hide')
        await nextFrame()

        menuItem.toggleSubMenu()
        await nextFrame()
        assert.isTrue(showSpy.calledOnce)

        menuItem.toggleSubMenu()
        await nextFrame()
        assert.isTrue(hideSpy.calledOnce)
      })

      it('should dispatch submenu-open event', async () => {
        const container = await withSubmenuFixture()
        const menuItem = container.querySelector('#parent-item') as UiMenuItem
        await nextFrame()

        setTimeout(() => menuItem.openSubMenu())
        const event = (await oneEvent(menuItem, 'submenu-open')) as CustomEvent<{ subMenu: UiSubMenu }>

        assert.instanceOf(event, CustomEvent)
        assert.isTrue(event.bubbles)
        assert.isTrue(event.composed)
        assert.isNotNull(event.detail.subMenu)
      })
    })

    describe('Click handling', () => {
      it('should handle click on regular menu item', async () => {
        const element = await basicFixture()
        const clickSpy = sinon.spy()
        element.addEventListener('click', clickSpy)

        element.click()
        await nextFrame()

        assert.isTrue(clickSpy.calledOnce)
      })

      it('should prevent default on submenu item click', async () => {
        const container = await withSubmenuFixture()
        const menuItem = container.querySelector('#parent-item') as UiMenuItem
        await nextFrame()

        const event = new MouseEvent('click', { bubbles: true, cancelable: true })
        let defaultPrevented = false
        let propagationStopped = false

        // Override preventDefault and stopPropagation to check if they were called
        const originalPreventDefault = event.preventDefault
        const originalStopPropagation = event.stopPropagation

        event.preventDefault = () => {
          defaultPrevented = true
          originalPreventDefault.call(event)
        }

        event.stopPropagation = () => {
          propagationStopped = true
          originalStopPropagation.call(event)
        }

        menuItem.dispatchEvent(event)

        assert.isTrue(defaultPrevented)
        assert.isTrue(propagationStopped)
      })
    })

    describe('Mouse interaction', () => {
      it('should open submenu on mouse enter', async () => {
        const container = await withSubmenuFixture()
        const menuItem = container.querySelector('#parent-item') as UiMenuItem
        const openSpy = sinon.spy(menuItem, 'openSubMenu')
        await nextFrame()

        const event = new MouseEvent('mouseenter')
        menuItem.dispatchEvent(event)

        assert.isTrue(openSpy.calledOnce)
      })

      it('should not open submenu on mouse enter if no submenu', async () => {
        const element = await basicFixture()
        const openSpy = sinon.spy(element, 'openSubMenu')

        const event = new MouseEvent('mouseenter')
        element.dispatchEvent(event)

        assert.isFalse(openSpy.called)
      })
    })

    describe('Accessibility', () => {
      it('should set aria-haspopup for items with submenu', async () => {
        const container = await withSubmenuFixture()
        const menuItem = container.querySelector('#parent-item') as UiMenuItem
        await nextFrame()

        assert.equal(menuItem.getAttribute('aria-haspopup'), 'true')
        assert.equal(menuItem.getAttribute('aria-expanded'), 'false')
      })

      it('should not set aria-haspopup for items without submenu', async () => {
        const element = await basicFixture()
        await nextFrame()

        assert.isNull(element.getAttribute('aria-haspopup'))
        assert.isNull(element.getAttribute('aria-expanded'))
      })

      it('should update aria-expanded when submenu opens/closes', async () => {
        const container = await withSubmenuFixture()
        const menuItem = container.querySelector('#parent-item') as UiMenuItem
        await nextFrame()

        assert.equal(menuItem.getAttribute('aria-expanded'), 'false')

        menuItem.openSubMenu()
        await nextFrame()
        assert.equal(menuItem.getAttribute('aria-expanded'), 'true')

        menuItem.closeSubMenu()
        await nextFrame()
        assert.equal(menuItem.getAttribute('aria-expanded'), 'false')
      })
    })

    describe('Disabled state', () => {
      it('should handle disabled attribute', async () => {
        const element = await disabledFixture()
        await nextFrame()

        assert.isTrue(element.disabled)
        assert.isTrue(element.hasAttribute('disabled'))
      })

      it('should reflect disabled property to attribute', async () => {
        const element = await basicFixture()

        element.disabled = true
        await nextFrame()
        assert.equal(element.getAttribute('disabled'), '')

        element.disabled = false
        await nextFrame()
        assert.isFalse(element.hasAttribute('disabled'))
      })
    })

    describe('Slots', () => {
      it('should render content in slots', async () => {
        const element = await withSlotsFixture()
        await nextFrame()

        const startSlot = element.shadowRoot!.querySelector('slot[name="start"]')
        const endSlot = element.shadowRoot!.querySelector('slot[name="end"]')
        const endTextSlot = element.shadowRoot!.querySelector('slot[name="end-text"]')

        assert.isNotNull(startSlot)
        assert.isNotNull(endSlot)
        assert.isNotNull(endTextSlot)
      })

      it('should show arrow icon for items with submenu', async () => {
        const container = await withSubmenuFixture()
        const menuItem = container.querySelector('#parent-item') as UiMenuItem
        await nextFrame()

        const arrow = menuItem.shadowRoot!.querySelector('.menu-item-arrow')
        assert.isNotNull(arrow)
        assert.equal(arrow!.textContent, 'arrow_right')
      })

      it('should not show arrow icon for items without submenu', async () => {
        const element = await basicFixture()
        await nextFrame()

        const arrow = element.shadowRoot!.querySelector('.menu-item-arrow')
        assert.isNull(arrow)
      })
    })

    describe('Edge cases', () => {
      it('should handle missing submenu element gracefully', async () => {
        const element: UiMenuItem = await fixture(html`<ui-menu-item submenu="nonexistent">Test</ui-menu-item>`)
        await nextFrame()

        assert.isFalse(element.hasSubMenu)
        assert.isNull(element.subMenuElement)

        // Should not throw
        element.openSubMenu()
        element.closeSubMenu()
        element.toggleSubMenu()
      })

      it('should handle multiple submenu property changes', async () => {
        const element = await basicFixture()

        element.submenu = 'test1'
        await nextFrame()

        element.submenu = 'test2'
        await nextFrame()

        element.submenu = undefined
        await nextFrame()

        // Should not throw or cause issues
        assert.isUndefined(element.submenu)
      })
    })

    describe('Rendering', () => {
      it('should render with correct CSS classes', async () => {
        const element = await basicFixture()
        await nextFrame()

        const surface = element.shadowRoot!.querySelector('.surface')
        assert.isNotNull(surface)
        assert.isTrue(surface!.classList.contains('menu-item'))
      })

      it('should render focus ring and ripple', async () => {
        const element = await basicFixture()
        await nextFrame()

        const focusRing = element.shadowRoot!.querySelector('md-focus-ring')
        const ripple = element.shadowRoot!.querySelector('ui-ripple')

        assert.isNotNull(focusRing)
        assert.isNotNull(ripple)
      })
    })
  })
})
