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

expect.extend(toHaveNoViolations)

// Import the components
import './accordion'
import './accordionitem'

// Import the component classes for type checking
import { PktAccordion } from './accordion'
import { PktAccordionItem } from './accordionitem'

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

// Helper function to create accordion markup
const createAccordion = async (
  accordionProps = '',
  accordionItemProps = '',
  content = 'Test content',
) => {
  const container = document.createElement('div')
  container.innerHTML = `
    <pkt-accordion ${accordionProps}>
      <pkt-accordion-item id="test-item" title="Test Title" ${accordionItemProps}>
        ${content}
      </pkt-accordion-item>
    </pkt-accordion>
  `
  document.body.appendChild(container)
  await waitForCustomElements()
  return container
}

// Helper function to create multiple accordion items
const createAccordionWithMultipleItems = async (accordionProps = '') => {
  const container = document.createElement('div')
  container.innerHTML = `
    <pkt-accordion ${accordionProps}>
      <pkt-accordion-item id="item1" title="Title 1">
        Content 1
      </pkt-accordion-item>
      <pkt-accordion-item id="item2" title="Title 2">
        Content 2
      </pkt-accordion-item>
      <pkt-accordion-item id="item3" title="Title 3">
        Content 3
      </pkt-accordion-item>
    </pkt-accordion>
  `
  document.body.appendChild(container)
  await waitForCustomElements()
  return container
}

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

describe('PktAccordion', () => {
  describe('Rendering and basic functionality', () => {
    test('renders without errors', async () => {
      const container = await createAccordion()

      const accordion = container.querySelector('pkt-accordion') as PktAccordion
      expect(accordion).toBeInTheDocument()

      await accordion.updateComplete
      expect(accordion.shadowRoot).toBeTruthy()
    })

    test('renders children accordion items', async () => {
      const container = await createAccordionWithMultipleItems()

      const accordionItems = container.querySelectorAll('pkt-accordion-item')
      expect(accordionItems).toHaveLength(3)

      // Verify content is rendered
      expect(container.textContent).toContain('Title 1')
      expect(container.textContent).toContain('Content 1')
      expect(container.textContent).toContain('Title 2')
      expect(container.textContent).toContain('Content 2')
      expect(container.textContent).toContain('Title 3')
      expect(container.textContent).toContain('Content 3')
    })

    test('applies default properties correctly', async () => {
      const container = await createAccordion()

      const accordion = container.querySelector('pkt-accordion') as PktAccordion
      await accordion.updateComplete

      expect(accordion.compact).toBe(false)
      expect(accordion.skin).toBe('borderless')
      expect(accordion.name).toBe('')
      expect(accordion.ariaLabelledBy).toBe('')

      const accordionDiv = accordion.shadowRoot?.querySelector('.pkt-accordion')
      expect(accordionDiv).toHaveClass('pkt-accordion')
      expect(accordionDiv).toHaveClass('pkt-accordion--borderless')
      expect(accordionDiv).not.toHaveClass('pkt-accordion--compact')
    })
  })

  describe('Properties and attributes', () => {
    test('applies compact property correctly', async () => {
      const container = await createAccordion('compact')

      const accordion = container.querySelector('pkt-accordion') as PktAccordion
      await accordion.updateComplete

      expect(accordion.compact).toBe(true)
      expect(accordion.hasAttribute('compact')).toBe(true)

      const accordionDiv = accordion.shadowRoot?.querySelector('.pkt-accordion')
      expect(accordionDiv).toHaveClass('pkt-accordion--compact')
    })

    test('applies different skin properties correctly', async () => {
      const skins = ['borderless', 'outlined', 'beige', 'blue']

      for (const skin of skins) {
        const container = await createAccordion(`skin="${skin}"`)
        const accordion = container.querySelector('pkt-accordion') as PktAccordion
        await accordion.updateComplete

        expect(accordion.skin).toBe(skin)
        expect(accordion.getAttribute('skin')).toBe(skin)

        const accordionDiv = accordion.shadowRoot?.querySelector('.pkt-accordion')
        expect(accordionDiv).toHaveClass(`pkt-accordion--${skin}`)

        // Cleanup for next iteration
        container.remove()
      }
    })

    test('applies aria-labelledby correctly', async () => {
      const container = await createAccordion('aria-labelledby="test-heading"')

      const accordion = container.querySelector('pkt-accordion') as PktAccordion
      await accordion.updateComplete

      expect(accordion.ariaLabelledBy).toBe('test-heading')
      expect(accordion.getAttribute('aria-labelledby')).toBe('test-heading')

      const accordionDiv = accordion.shadowRoot?.querySelector('.pkt-accordion')
      expect(accordionDiv?.getAttribute('aria-labelledby')).toBe('test-heading')
    })

    test('applies name property and updates accordion items', async () => {
      const container = await createAccordionWithMultipleItems('name="test-group"')

      const accordion = container.querySelector('pkt-accordion') as PktAccordion
      const accordionItems = container.querySelectorAll('pkt-accordion-item')
      await accordion.updateComplete

      expect(accordion.name).toBe('test-group')
      expect(accordion.getAttribute('name')).toBe('test-group')

      // All accordion items should inherit the name
      accordionItems.forEach((item) => {
        expect(item.getAttribute('name')).toBe('test-group')
      })
    })

    test('updates accordion item names when name property changes', async () => {
      const container = await createAccordionWithMultipleItems()

      const accordion = container.querySelector('pkt-accordion') as PktAccordion
      const accordionItems = container.querySelectorAll('pkt-accordion-item')
      await accordion.updateComplete

      // Initially no name
      expect(accordion.name).toBe('')
      accordionItems.forEach((item) => {
        expect(item.getAttribute('name')).toBe(null)
      })

      // Update name property
      accordion.name = 'updated-group'
      await accordion.updateComplete

      // All accordion items should now have the updated name
      accordionItems.forEach((item) => {
        expect(item.getAttribute('name')).toBe('updated-group')
      })
    })

    test('does not override existing name on accordion items', async () => {
      const container = document.createElement('div')
      container.innerHTML = `
        <pkt-accordion name="group-name">
          <pkt-accordion-item id="item1" title="Title 1" name="existing-name">
            Content 1
          </pkt-accordion-item>
          <pkt-accordion-item id="item2" title="Title 2">
            Content 2
          </pkt-accordion-item>
        </pkt-accordion>
      `
      document.body.appendChild(container)
      await waitForCustomElements()

      const accordion = container.querySelector('pkt-accordion') as PktAccordion
      const accordionItems = container.querySelectorAll('pkt-accordion-item')
      await accordion.updateComplete

      // First item should keep its existing name
      expect(accordionItems[0].getAttribute('name')).toBe('existing-name')
      // Second item should get the accordion's name
      expect(accordionItems[1].getAttribute('name')).toBe('group-name')
    })
  })

  describe('Dynamic content handling', () => {
    test('handles dynamically added accordion items', async () => {
      const container = await createAccordion('name="dynamic-group"')

      const accordion = container.querySelector('pkt-accordion') as PktAccordion
      await accordion.updateComplete

      // Add a new accordion item
      const newItem = document.createElement('pkt-accordion-item') as PktAccordionItem
      newItem.setAttribute('id', 'dynamic-item')
      newItem.setAttribute('title', 'Dynamic Title')
      newItem.textContent = 'Dynamic Content'
      accordion.appendChild(newItem)

      // Wait for the slot change to propagate
      await new Promise((resolve) => setTimeout(resolve, 100))
      await accordion.updateComplete

      expect(newItem.getAttribute('name')).toBe('dynamic-group')
    })
  })
})

describe('PktAccordionItem', () => {
  describe('Rendering and basic functionality', () => {
    test('renders without errors', async () => {
      const container = await createAccordion()

      const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
      expect(accordionItem).toBeInTheDocument()

      await accordionItem.updateComplete
      expect(accordionItem).toBeTruthy()

      const details = accordionItem.querySelector('details')
      expect(details).toBeInTheDocument()
    })

    test('renders with correct structure', async () => {
      const container = await createAccordion('', '', 'Test accordion content')

      const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
      await accordionItem.updateComplete

      const details = accordionItem.querySelector('details')
      const summary = details?.querySelector('summary')
      const content = details?.querySelector('.pkt-accordion-item__content')
      const contentInner = content?.querySelector('.pkt-accordion-item__content-inner')

      expect(summary).toHaveClass('pkt-accordion-item__title')
      expect(summary?.textContent).toContain('Test Title')
      expect(content?.getAttribute('role')).toBe('region')
      expect(contentInner?.textContent).toContain('Test accordion content')
      expect(contentInner).toBeInTheDocument()
    })

    test('renders icon correctly', async () => {
      const container = await createAccordion()

      const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
      await accordionItem.updateComplete

      const icon = accordionItem.querySelector('pkt-icon')
      expect(icon).toBeInTheDocument()
      expect(icon?.getAttribute('name')).toBe('chevron-thin-down')
      expect(icon).toHaveClass('pkt-accordion-item__icon')
      expect(icon?.getAttribute('aria-hidden')).toBe('true')
    })
  })

  describe('Properties and attributes', () => {
    test('applies default properties correctly', async () => {
      const container = await createAccordion()

      const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
      await accordionItem.updateComplete

      expect(accordionItem.defaultOpen).toBe(false)
      expect(accordionItem.title).toBe('Test Title')
      expect(accordionItem.skin).toBe(undefined)

      const details = accordionItem.querySelector('details')
      expect(details?.hasAttribute('open')).toBe(false)
    })

    test('applies different skin properties correctly', async () => {
      const skins = ['borderless', 'outlined', 'beige', 'blue']

      for (const skin of skins) {
        const container = await createAccordion('', `skin="${skin}"`)
        const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
        await accordionItem.updateComplete

        expect(accordionItem.skin).toBe(skin)
        expect(accordionItem.getAttribute('skin')).toBe(skin)

        const details = accordionItem.querySelector('details')
        expect(details).toHaveClass(`pkt-accordion-item--${skin}`)

        // Cleanup for next iteration
        container.remove()
      }
    })

    test('handles defaultOpen property', async () => {
      const container = await createAccordion('', '')

      const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem

      // Test that setting defaultOpen to true sets isOpen to true
      accordionItem.defaultOpen = true
      await accordionItem.updateComplete

      // Manually trigger what firstUpdated should do
      if (accordionItem.defaultOpen) {
        accordionItem.isOpen = true
      }
      await accordionItem.updateComplete

      expect(accordionItem.defaultOpen).toBe(true)
      // When defaultOpen is true, isOpen should be set to true
      expect(accordionItem.isOpen).toBe(true)

      const details = accordionItem.querySelector('details')
      expect(details?.hasAttribute('open')).toBe(true)
    })

    test('handles title property updates', async () => {
      const container = await createAccordion()

      const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
      await accordionItem.updateComplete

      const summary = accordionItem.querySelector('summary')
      expect(summary?.textContent).toContain('Test Title')

      // Update title
      accordionItem.title = 'Updated Title'
      await accordionItem.updateComplete

      expect(summary?.textContent).toContain('Updated Title')
    })
  })

  describe('Interaction and state management', () => {
    test('toggles open state when isOpen property changes', async () => {
      const container = await createAccordion()

      const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
      await accordionItem.updateComplete

      const details = accordionItem.querySelector('details')
      expect(details?.hasAttribute('open')).toBe(false)

      // Set isOpen to true
      accordionItem.isOpen = true
      await accordionItem.updateComplete

      expect(details?.hasAttribute('open')).toBe(true)

      // Set isOpen to false
      accordionItem.isOpen = false
      await accordionItem.updateComplete

      expect(details?.hasAttribute('open')).toBe(false)
    })

    test('respects name attribute for grouped behavior', async () => {
      const container = await createAccordionWithMultipleItems('name="test-group"')

      const accordionItems = container.querySelectorAll(
        'pkt-accordion-item',
      ) as NodeListOf<PktAccordionItem>
      await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))

      const details = Array.from(accordionItems)
        .map((item) => item.querySelector('details'))
        .filter(Boolean) as HTMLDetailsElement[]

      // Open first item programmatically
      accordionItems[0].isOpen = true
      await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))

      expect(details[0].hasAttribute('open')).toBe(true)
      expect(details[1].hasAttribute('open')).toBe(false)
      expect(details[2].hasAttribute('open')).toBe(false)

      // Open second item (should close first due to grouping)
      accordionItems[1].isOpen = true
      await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))

      // Note: If grouping behavior is implemented, first item should close
      // For now, let's test basic functionality
      expect(details[1].hasAttribute('open')).toBe(true)
    })

    test('allows multiple items open when no name grouping', async () => {
      const container = await createAccordionWithMultipleItems()

      const accordionItems = container.querySelectorAll(
        'pkt-accordion-item',
      ) as NodeListOf<PktAccordionItem>
      await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))

      const details = Array.from(accordionItems)
        .map((item) => item.querySelector('details'))
        .filter(Boolean) as HTMLDetailsElement[]

      // Open first and third items programmatically
      accordionItems[0].isOpen = true
      accordionItems[2].isOpen = true
      await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))

      // Both should remain open since there's no grouping
      expect(details[0].hasAttribute('open')).toBe(true)
      expect(details[1].hasAttribute('open')).toBe(false)
      expect(details[2].hasAttribute('open')).toBe(true)
    })
  })
})

describe('Integration tests', () => {
  test('accordion and accordion items work together correctly', async () => {
    const container = await createAccordionWithMultipleItems(
      'skin="outlined" compact name="integration-test"',
    )

    const accordion = container.querySelector('pkt-accordion') as PktAccordion
    const accordionItems = container.querySelectorAll(
      'pkt-accordion-item',
    ) as NodeListOf<PktAccordionItem>
    await accordion.updateComplete
    await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))

    // Verify accordion properties
    expect(accordion.skin).toBe('outlined')
    expect(accordion.compact).toBe(true)
    expect(accordion.name).toBe('integration-test')

    const accordionDiv = accordion.shadowRoot?.querySelector('.pkt-accordion')
    expect(accordionDiv).toHaveClass('pkt-accordion')
    expect(accordionDiv).toHaveClass('pkt-accordion--outlined')
    expect(accordionDiv).toHaveClass('pkt-accordion--compact')

    // Verify all items have inherited name
    accordionItems.forEach((item) => {
      expect(item.getAttribute('name')).toBe('integration-test')
    })

    const details = Array.from(accordionItems)
      .map((item) => item.querySelector('details'))
      .filter(Boolean) as HTMLDetailsElement[]

    // Open items programmatically to test functionality
    accordionItems[0].isOpen = true
    accordionItems[2].isOpen = true
    await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))

    // Test that properties are reflected to attributes
    expect(details[0].hasAttribute('open')).toBe(true)
    expect(details[2].hasAttribute('open')).toBe(true)
  })

  test('handles mixed open states correctly', async () => {
    const container = document.createElement('div')
    container.innerHTML = `
      <pkt-accordion>
        <pkt-accordion-item id="item1" title="Title 1" default-open>
          Content 1
        </pkt-accordion-item>
        <pkt-accordion-item id="item2" title="Title 2">
          Content 2
        </pkt-accordion-item>
      </pkt-accordion>
    `
    document.body.appendChild(container)
    await waitForCustomElements()

    const accordionItems = container.querySelectorAll(
      'pkt-accordion-item',
    ) as NodeListOf<PktAccordionItem>

    // Set defaultOpen and isOpen programmatically to test the functionality
    accordionItems[0].defaultOpen = true
    accordionItems[0].isOpen = true

    await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))

    const details = Array.from(accordionItems)
      .map((item) => item.querySelector('details'))
      .filter(Boolean) as HTMLDetailsElement[]

    // Item 1 should be open due to defaultOpen
    expect(details[0].hasAttribute('open')).toBe(true)
    // Item 2 should be closed
    expect(details[1].hasAttribute('open')).toBe(false)
  })
})

describe('Accessibility', () => {
  test('has correct ARIA attributes', async () => {
    const container = document.createElement('div')
    container.innerHTML = `
      <h2 id="accordion-heading">Main Accordion</h2>
      <pkt-accordion aria-labelledby="accordion-heading">
        <pkt-accordion-item id="test-item" title="Test Title">
          Test content
        </pkt-accordion-item>
      </pkt-accordion>
    `
    document.body.appendChild(container)
    await waitForCustomElements()

    const accordion = container.querySelector('pkt-accordion') as PktAccordion
    const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
    await accordion.updateComplete
    await accordionItem.updateComplete

    const accordionDiv = accordion.shadowRoot?.querySelector('.pkt-accordion')
    const content = accordionItem.querySelector('.pkt-accordion-item__content')
    const icon = accordionItem.querySelector('pkt-icon')

    expect(accordionDiv?.getAttribute('aria-labelledby')).toBe('accordion-heading')
    expect(content?.getAttribute('role')).toBe('region')
    expect(icon?.getAttribute('aria-hidden')).toBe('true')
  })

  test('renders with no WCAG errors with axe - simple accordion', async () => {
    const container = await createAccordion()

    // Wait for all components to be fully rendered
    const accordion = container.querySelector('pkt-accordion') as PktAccordion
    const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
    await accordion.updateComplete
    await accordionItem.updateComplete

    const results = await axe(container)
    expect(results).toHaveNoViolations()
  })

  test('renders with no WCAG errors with axe - complex accordion', async () => {
    const container = document.createElement('div')
    container.innerHTML = `
      <h2 id="accordion-heading">Test Accordion Heading</h2>
      <pkt-accordion skin="outlined" compact aria-labelledby="accordion-heading" name="test-group">
        <pkt-accordion-item id="item1" title="First Item" default-open>
          <p>This is the first accordion item content with <a href="#">a link</a>.</p>
        </pkt-accordion-item>
        <pkt-accordion-item id="item2" title="Second Item">
          <div>
            <h3>Nested content</h3>
            <ul>
              <li>List item 1</li>
              <li>List item 2</li>
            </ul>
          </div>
        </pkt-accordion-item>
        <pkt-accordion-item id="item3" title="Third Item" skin="blue">
          <form>
            <label for="test-input">Test Input:</label>
            <input type="text" id="test-input" name="test" />
            <button type="submit">Submit</button>
          </form>
        </pkt-accordion-item>
      </pkt-accordion>
    `
    document.body.appendChild(container)
    await waitForCustomElements()

    const accordion = container.querySelector('pkt-accordion') as PktAccordion
    const accordionItems = container.querySelectorAll(
      'pkt-accordion-item',
    ) as NodeListOf<PktAccordionItem>
    await accordion.updateComplete
    await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))

    const results = await axe(container)
    expect(results).toHaveNoViolations()
  })

  test('renders with no WCAG errors with axe - multiple accordions', async () => {
    const container = document.createElement('div')
    container.innerHTML = `
      <div>
        <h2 id="first-accordion-heading">First Accordion</h2>
        <pkt-accordion aria-labelledby="first-accordion-heading" name="first-group">
          <pkt-accordion-item id="first-item1" title="First Group Item 1">
            Content for first group
          </pkt-accordion-item>
          <pkt-accordion-item id="first-item2" title="First Group Item 2">
            More content for first group
          </pkt-accordion-item>
        </pkt-accordion>

        <h2 id="second-accordion-heading">Second Accordion</h2>
        <pkt-accordion aria-labelledby="second-accordion-heading" name="second-group" skin="beige">
          <pkt-accordion-item id="second-item1" title="Second Group Item 1">
            Content for second group
          </pkt-accordion-item>
          <pkt-accordion-item id="second-item2" title="Second Group Item 2">
            More content for second group
          </pkt-accordion-item>
        </pkt-accordion>
      </div>
    `
    document.body.appendChild(container)
    await waitForCustomElements()

    const accordions = container.querySelectorAll('pkt-accordion') as NodeListOf<PktAccordion>
    const accordionItems = container.querySelectorAll(
      'pkt-accordion-item',
    ) as NodeListOf<PktAccordionItem>
    await Promise.all(Array.from(accordions).map((acc) => acc.updateComplete))
    await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))

    const results = await axe(container)
    expect(results).toHaveNoViolations()
  })
})
