import '@testing-library/jest-dom'
import { axe, toHaveNoViolations } from 'jest-axe'
import { vi } from 'vitest'

expect.extend(toHaveNoViolations)

// Import the components
import './tabs'
import './tabitem'

// Import component classes
import { PktTabs } from './tabs'

const waitForCustomElements = async () => {
  await Promise.all([
    customElements.whenDefined('pkt-tabs'),
    customElements.whenDefined('pkt-tab-item'),
  ])
}

// Helper function to create tabs markup
const createTabs = async (tabsProps = '', tabItemsMarkup = '') => {
  const container = document.createElement('div')
  const defaultMarkup = `
    <pkt-tab-item index="0" active>Tab 1</pkt-tab-item>
    <pkt-tab-item index="1">Tab 2</pkt-tab-item>
    <pkt-tab-item index="2">Tab 3</pkt-tab-item>
  `
  container.innerHTML = `
    <pkt-tabs ${tabsProps}>
      ${tabItemsMarkup || defaultMarkup}
    </pkt-tabs>
  `
  document.body.appendChild(container)
  await waitForCustomElements()

  const tabs = container.querySelector('pkt-tabs') as PktTabs
  await tabs.updateComplete

  // Wait for tab items to be updated
  const tabItems = container.querySelectorAll('pkt-tab-item')
  await Promise.all(
    Array.from(tabItems).map(
      (item) => (item as HTMLElement & { updateComplete: Promise<boolean> }).updateComplete,
    ),
  )

  return container
}

// Cleanup after each test
afterEach(() => {
  document.body.innerHTML = ''
})

describe('PktTabs', () => {
  describe('Rendering and basic functionality', () => {
    test('renders without errors', async () => {
      const container = await createTabs()
      const tabs = container.querySelector('pkt-tabs') as PktTabs
      expect(tabs).toBeInTheDocument()
    })

    test('renders tab items from children', async () => {
      const container = await createTabs()
      const tabItems = container.querySelectorAll('pkt-tab-item')
      expect(tabItems).toHaveLength(3)
      expect(tabItems[0].textContent?.trim()).toBe('Tab 1')
      expect(tabItems[1].textContent?.trim()).toBe('Tab 2')
      expect(tabItems[2].textContent?.trim()).toBe('Tab 3')
    })

    test('applies active class to the active tab', async () => {
      const container = await createTabs()
      const tabItems = container.querySelectorAll('pkt-tab-item')

      const firstTabButton = tabItems[0].querySelector('button, a')
      const secondTabButton = tabItems[1].querySelector('button, a')

      expect(firstTabButton).toHaveClass('active')
      expect(secondTabButton).not.toHaveClass('active')
    })

    test('renders as button when no href is provided', async () => {
      const container = await createTabs()
      const firstTabItem = container.querySelector('pkt-tab-item')
      const button = firstTabItem?.querySelector('button')
      const link = firstTabItem?.querySelector('a')

      expect(button).toBeInTheDocument()
      expect(link).not.toBeInTheDocument()
      expect(button?.tagName).toBe('BUTTON')
    })

    test('renders as link when href is provided', async () => {
      const container = await createTabs(
        '',
        '<pkt-tab-item index="0" href="/first">First Tab</pkt-tab-item>',
      )
      const firstTabItem = container.querySelector('pkt-tab-item')
      const link = firstTabItem?.querySelector('a')
      const button = firstTabItem?.querySelector('button')

      expect(link).toBeInTheDocument()
      expect(button).not.toBeInTheDocument()
      expect(link?.getAttribute('href')).toBe('/first')
    })

    test('applies default arrowNav property correctly', async () => {
      const container = await createTabs()
      const tabs = container.querySelector('pkt-tabs') as PktTabs
      await tabs.updateComplete

      expect(tabs.arrowNav).toBe(true)
      expect(tabs.disableArrowNav).toBe(false)
    })

    test('applies custom arrowNav property correctly', async () => {
      const container = await createTabs('disable-arrow-nav')
      const tabs = container.querySelector('pkt-tabs') as PktTabs
      await tabs.updateComplete

      // When disable-arrow-nav is present, effective arrowNav should be false
      expect(tabs.disableArrowNav).toBe(true)
      expect(tabs.arrowNav).toBe(true) // arrowNav prop itself is still true by default
    })

    test('applies disableArrowNav property correctly', async () => {
      const container = await createTabs('disable-arrow-nav="true"')
      const tabs = container.querySelector('pkt-tabs') as PktTabs
      await tabs.updateComplete

      expect(tabs.disableArrowNav).toBe(true)
    })

    test('applies separator class on tabs root when separator icon is provided', async () => {
      const container = await createTabs('separator-icon-name="arrow-right"')
      const tabsRoot = container.querySelector('.pkt-tabs')
      expect(tabsRoot).toHaveClass('pkt-tabs--with-separator')
    })

    test('uses separator-icon-path when both separator props are provided', async () => {
      const container = await createTabs(
        'separator-icon-name="arrow-right" separator-icon-path="/custom.svg"',
      )
      const separatorImage = container.querySelector('.pkt-tabs__separator img')
      expect(separatorImage?.getAttribute('src')).toBe('/custom.svg')
    })
  })

  describe('Tab item props', () => {
    test('renders icon when icon prop is provided', async () => {
      const container = await createTabs(
        '',
        '<pkt-tab-item index="0" icon="user">Tab with icon</pkt-tab-item>',
      )
      const icon = container.querySelector('pkt-icon')
      expect(icon).toBeInTheDocument()
      expect(icon?.getAttribute('name')).toBe('user')
      expect(icon).toHaveClass('pkt-icon--small')
    })

    test('renders tag when tag prop is provided', async () => {
      const container = await createTabs(
        '',
        '<pkt-tab-item index="0" tag="New" tag-skin="blue">Tab with tag</pkt-tab-item>',
      )
      const tag = container.querySelector('pkt-tag')
      expect(tag).toBeInTheDocument()
      expect(tag?.textContent?.trim()).toBe('New')
      expect(tag?.getAttribute('skin')).toBe('blue')
    })

    test('applies controls attribute when provided', async () => {
      const container = await createTabs(
        '',
        '<pkt-tab-item index="0" controls="panel-1">Tab 1</pkt-tab-item>',
      )
      const button = container.querySelector('button')
      expect(button?.getAttribute('aria-controls')).toBe('panel-1')
    })
  })

  describe('Click interactions', () => {
    test('dispatches tab-selected event when tab is clicked', async () => {
      const container = await createTabs()
      const tabs = container.querySelector('pkt-tabs') as PktTabs

      const eventListener = vi.fn()
      tabs.addEventListener('tab-selected', eventListener)

      const secondTabItem = container.querySelectorAll('pkt-tab-item')[1]
      const button = secondTabItem.querySelector('button')
      button?.click()

      expect(eventListener).toHaveBeenCalled()
      expect(eventListener.mock.calls[0][0].detail.index).toBe(1)
    })

    test('dispatches tab-selected event with correct index for each tab', async () => {
      const container = await createTabs()
      const tabs = container.querySelector('pkt-tabs') as PktTabs

      const eventListener = vi.fn()
      tabs.addEventListener('tab-selected', eventListener)

      const tabItems = container.querySelectorAll('pkt-tab-item')

      // Click first tab
      tabItems[0].querySelector('button')?.click()
      expect(eventListener).toHaveBeenCalledTimes(1)
      expect(eventListener.mock.calls[0][0].detail.index).toBe(0)

      // Click third tab
      tabItems[2].querySelector('button')?.click()
      expect(eventListener).toHaveBeenCalledTimes(2)
      expect(eventListener.mock.calls[1][0].detail.index).toBe(2)
    })
  })

  describe('Keyboard navigation', () => {
    test('handles ArrowRight keyboard navigation', async () => {
      const container = await createTabs()
      const tabItems = container.querySelectorAll('pkt-tab-item')

      const firstButton = tabItems[0].querySelector('button') as HTMLButtonElement
      const secondButton = tabItems[1].querySelector('button') as HTMLButtonElement

      firstButton.focus()
      expect(document.activeElement).toBe(firstButton)

      // Simulate ArrowRight key
      const keyEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })
      firstButton.dispatchEvent(keyEvent)

      // Wait for focus to change
      await new Promise((resolve) => setTimeout(resolve, 50))

      expect(document.activeElement).toBe(secondButton)
    })

    test('handles ArrowLeft keyboard navigation', async () => {
      const container = await createTabs()
      const tabItems = container.querySelectorAll('pkt-tab-item')

      const firstButton = tabItems[0].querySelector('button') as HTMLButtonElement
      const secondButton = tabItems[1].querySelector('button') as HTMLButtonElement

      secondButton.focus()
      expect(document.activeElement).toBe(secondButton)

      // Simulate ArrowLeft key
      const keyEvent = new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true })
      secondButton.dispatchEvent(keyEvent)

      // Wait for focus to change
      await new Promise((resolve) => setTimeout(resolve, 50))

      expect(document.activeElement).toBe(firstButton)
    })

    test('stays on last tab when navigating past it with ArrowRight', async () => {
      const container = await createTabs()
      const tabItems = container.querySelectorAll('pkt-tab-item')

      const thirdButton = tabItems[2].querySelector('button') as HTMLButtonElement

      thirdButton.focus()
      expect(document.activeElement).toBe(thirdButton)

      // Simulate ArrowRight key
      const keyEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })
      thirdButton.dispatchEvent(keyEvent)

      // Wait
      await new Promise((resolve) => setTimeout(resolve, 50))

      // Should stay on third tab
      expect(document.activeElement).toBe(thirdButton)
    })

    test('stays on first tab when navigating before it with ArrowLeft', async () => {
      const container = await createTabs()
      const tabItems = container.querySelectorAll('pkt-tab-item')

      const firstButton = tabItems[0].querySelector('button') as HTMLButtonElement

      firstButton.focus()
      expect(document.activeElement).toBe(firstButton)

      // Simulate ArrowLeft key
      const keyEvent = new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true })
      firstButton.dispatchEvent(keyEvent)

      // Wait
      await new Promise((resolve) => setTimeout(resolve, 50))

      // Should stay on first tab
      expect(document.activeElement).toBe(firstButton)
    })

    test('dispatches tab-selected event when Space key is pressed', async () => {
      const container = await createTabs()
      const tabs = container.querySelector('pkt-tabs') as PktTabs

      const eventListener = vi.fn()
      tabs.addEventListener('tab-selected', eventListener)

      const secondTabItem = container.querySelectorAll('pkt-tab-item')[1]
      const button = secondTabItem.querySelector('button') as HTMLButtonElement

      // Simulate Space key
      const keyEvent = new KeyboardEvent('keydown', { key: ' ', bubbles: true })
      button.dispatchEvent(keyEvent)

      // Wait
      await new Promise((resolve) => setTimeout(resolve, 50))

      expect(eventListener).toHaveBeenCalled()
      expect(eventListener.mock.calls[0][0].detail.index).toBe(1)
    })

    test('dispatches tab-selected event when ArrowDown key is pressed', async () => {
      const container = await createTabs()
      const tabs = container.querySelector('pkt-tabs') as PktTabs

      const eventListener = vi.fn()
      tabs.addEventListener('tab-selected', eventListener)

      const secondTabItem = container.querySelectorAll('pkt-tab-item')[1]
      const button = secondTabItem.querySelector('button') as HTMLButtonElement

      // Simulate ArrowDown key
      const keyEvent = new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
      button.dispatchEvent(keyEvent)

      // Wait
      await new Promise((resolve) => setTimeout(resolve, 50))

      expect(eventListener).toHaveBeenCalled()
      expect(eventListener.mock.calls[0][0].detail.index).toBe(1)
    })

    test('disables keyboard navigation when arrowNav is false', async () => {
      const container = await createTabs('disable-arrow-nav')
      const tabs = container.querySelector('pkt-tabs') as PktTabs
      await tabs.updateComplete

      const tabItems = container.querySelectorAll('pkt-tab-item')

      const firstButton = tabItems[0].querySelector('button') as HTMLButtonElement

      firstButton.focus()
      expect(document.activeElement).toBe(firstButton)

      // Simulate ArrowRight key
      const keyEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })
      firstButton.dispatchEvent(keyEvent)

      // Wait
      await new Promise((resolve) => setTimeout(resolve, 50))

      // Should NOT move to second tab
      expect(document.activeElement).toBe(firstButton)
    })

    test('disables keyboard navigation when disableArrowNav is true', async () => {
      const container = await createTabs('disable-arrow-nav="true"')
      const tabItems = container.querySelectorAll('pkt-tab-item')

      const firstButton = tabItems[0].querySelector('button') as HTMLButtonElement

      firstButton.focus()
      expect(document.activeElement).toBe(firstButton)

      // Simulate ArrowRight key
      const keyEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })
      firstButton.dispatchEvent(keyEvent)

      // Wait
      await new Promise((resolve) => setTimeout(resolve, 50))

      // Should NOT move to second tab
      expect(document.activeElement).toBe(firstButton)
    })

    test('disableArrowNav overrides arrowNav when both are set', async () => {
      const container = await createTabs('arrow-nav="true" disable-arrow-nav="true"')
      const tabItems = container.querySelectorAll('pkt-tab-item')

      const firstButton = tabItems[0].querySelector('button') as HTMLButtonElement

      firstButton.focus()

      // Simulate ArrowRight key
      const keyEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })
      firstButton.dispatchEvent(keyEvent)

      // Wait
      await new Promise((resolve) => setTimeout(resolve, 50))

      // Should NOT move to second tab (disableArrowNav overrides arrowNav)
      expect(document.activeElement).toBe(firstButton)
    })

    test('skips disabled tabs during ArrowRight navigation', async () => {
      const container = await createTabs(
        '',
        `
        <pkt-tab-item index="0" active>Tab 1</pkt-tab-item>
        <pkt-tab-item index="1" disabled>Tab 2</pkt-tab-item>
        <pkt-tab-item index="2">Tab 3</pkt-tab-item>
      `,
      )
      const tabItems = container.querySelectorAll('pkt-tab-item')
      const firstButton = tabItems[0].querySelector('button') as HTMLButtonElement
      const thirdButton = tabItems[2].querySelector('button') as HTMLButtonElement

      firstButton.focus()
      const keyEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })
      firstButton.dispatchEvent(keyEvent)
      await new Promise((resolve) => setTimeout(resolve, 50))

      expect(document.activeElement).toBe(thirdButton)
    })
  })

  describe('Disabled tabs', () => {
    test('does not dispatch tab-selected for disabled button tab', async () => {
      const container = await createTabs(
        '',
        `
        <pkt-tab-item index="0" active>Tab 1</pkt-tab-item>
        <pkt-tab-item index="1" disabled>Tab 2</pkt-tab-item>
      `,
      )
      const tabs = container.querySelector('pkt-tabs') as PktTabs
      const eventListener = vi.fn()
      tabs.addEventListener('tab-selected', eventListener)

      const disabledButton = container.querySelectorAll('pkt-tab-item')[1].querySelector('button')
      disabledButton?.click()

      expect(disabledButton).toHaveAttribute('disabled')
      expect(eventListener).not.toHaveBeenCalled()
    })

    test('disabled link tab has aria-disabled and does not dispatch event', async () => {
      const container = await createTabs(
        '',
        `
        <pkt-tab-item index="0" href="/enabled" active>Enabled</pkt-tab-item>
        <pkt-tab-item index="1" href="/disabled" disabled>Disabled</pkt-tab-item>
      `,
      )
      const tabs = container.querySelector('pkt-tabs') as PktTabs
      const eventListener = vi.fn()
      tabs.addEventListener('tab-selected', eventListener)

      const disabledLink = container.querySelectorAll('pkt-tab-item')[1].querySelector('a') as HTMLAnchorElement
      disabledLink.click()
      disabledLink.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))

      expect(disabledLink).toHaveAttribute('aria-disabled', 'true')
      expect(disabledLink).toHaveAttribute('tabindex', '-1')
      expect(disabledLink).not.toHaveAttribute('href')
      expect(eventListener).not.toHaveBeenCalled()
    })

    test('disabled active tab is not exposed as selected', async () => {
      const container = await createTabs(
        '',
        `
        <pkt-tab-item index="0" active disabled>Disabled active</pkt-tab-item>
        <pkt-tab-item index="1">Enabled</pkt-tab-item>
      `,
      )
      const disabledButton = container.querySelector('pkt-tab-item button')

      expect(disabledButton).toHaveAttribute('aria-selected', 'false')
    })
  })

  describe('Accessibility', () => {
    test('should not have any accessibility violations', async () => {
      const container = await createTabs()
      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('sets correct ARIA attributes on buttons when arrowNav is true', async () => {
      const container = await createTabs()
      const tabItems = container.querySelectorAll('pkt-tab-item')

      const firstButton = tabItems[0].querySelector('button')
      const secondButton = tabItems[1].querySelector('button')

      expect(firstButton).toHaveAttribute('role', 'tab')
      expect(firstButton).toHaveAttribute('aria-selected', 'true')
      expect(secondButton).toHaveAttribute('role', 'tab')
      expect(secondButton).toHaveAttribute('aria-selected', 'false')
    })

    test('does not set tab role or aria-selected on links when arrowNav is false', async () => {
      const container = await createTabs(
        'disable-arrow-nav',
        `
        <pkt-tab-item index="0" href="/" active>Home</pkt-tab-item>
        <pkt-tab-item index="1" href="/about">About</pkt-tab-item>
      `,
      )
      await new Promise((resolve) => setTimeout(resolve, 50))
      const tabItems = container.querySelectorAll('pkt-tab-item')

      const homeLink = tabItems[0].querySelector('a')
      const aboutLink = tabItems[1].querySelector('a')

      expect(homeLink).not.toHaveAttribute('role')
      expect(homeLink).not.toHaveAttribute('aria-selected')
      expect(aboutLink).not.toHaveAttribute('role')
      expect(aboutLink).not.toHaveAttribute('aria-selected')
    })

    test('works with href links when arrowNav is false (no WCAG violations)', async () => {
      const container = await createTabs(
        'disable-arrow-nav',
        `
        <pkt-tab-item index="0" href="/" active>Home</pkt-tab-item>
        <pkt-tab-item index="1" href="/about">About</pkt-tab-item>
      `,
      )
      await new Promise((resolve) => setTimeout(resolve, 50))
      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('sets role="tablist" when arrowNav is true', async () => {
      const container = await createTabs()
      const tabs = container.querySelector('pkt-tabs') as PktTabs
      const tabList = tabs.querySelector('.pkt-tabs__list')

      expect(tabList).toHaveAttribute('role', 'tablist')
    })

    test('sets role="navigation" when arrowNav is false', async () => {
      const container = await createTabs('disable-arrow-nav')
      const tabs = container.querySelector('pkt-tabs') as PktTabs
      await tabs.updateComplete
      const tabList = tabs.querySelector('.pkt-tabs__list')

      expect(tabList).toHaveAttribute('role', 'navigation')
    })

    test('keeps tab semantics when separators are enabled', async () => {
      const container = await createTabs('separator-icon-name="arrow-right"')
      const tabList = container.querySelector('.pkt-tabs__list')
      const buttons = container.querySelectorAll('pkt-tab-item button')

      expect(tabList).toHaveAttribute('role', 'tablist')
      buttons.forEach((button) => {
        expect(button).toHaveAttribute('role', 'tab')
      })
    })

    test('renders decorative separators in DOM', async () => {
      const container = await createTabs('separator-icon-name="arrow-right"')
      expect(container.querySelectorAll('.pkt-tabs__separator')).toHaveLength(2)
    })
  })

  describe('Multiple tab items', () => {
    test('works with many tab items', async () => {
      const container = await createTabs(
        '',
        `
        <pkt-tab-item index="0" active>Tab 1</pkt-tab-item>
        <pkt-tab-item index="1">Tab 2</pkt-tab-item>
        <pkt-tab-item index="2">Tab 3</pkt-tab-item>
        <pkt-tab-item index="3">Tab 4</pkt-tab-item>
        <pkt-tab-item index="4">Tab 5</pkt-tab-item>
      `,
      )

      const tabItems = container.querySelectorAll('pkt-tab-item')
      expect(tabItems).toHaveLength(5)

      // Test navigation from first to last
      const firstButton = tabItems[0].querySelector('button') as HTMLButtonElement
      firstButton.focus()

      // Navigate through all tabs
      for (let i = 0; i < 4; i++) {
        const keyEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })
        ;(document.activeElement as HTMLElement).dispatchEvent(keyEvent)
        await new Promise((resolve) => setTimeout(resolve, 50))
      }

      const lastButton = tabItems[4].querySelector('button') as HTMLButtonElement
      expect(document.activeElement).toBe(lastButton)
    })
  })
})
