// Keyboard-a11y e2e for the canonical Select (INFRA-F68 a11y-03 / WCAG 2.1.1
// Level A "Keyboard"). Drives the isolated harness route
// /internal/select-keyboard-harness (one interactive SelectBoxLine, options
// Apple/Banana/Cherry) through the full open → arrow → enter → escape flow.
//
// These are true browser tests: teleported listbox, real focus, real key
// dispatch — none of which jsdom/vitest reproduces faithfully, which is why the
// keyboard handler on DropDownListSelect was silently unreachable before the fix.
//
// Lives under tests/ where vitest's glob would otherwise collect it, so it uses
// the same `process.env.VITEST` guard as tests/a11y/docs-pages.spec.ts: skipped
// under vitest, run for real via `pnpm test:select-keyboard` (Playwright).

import type { Page } from '@playwright/test'

const HARNESS = '/internal/select-keyboard-harness'

if (process.env.VITEST) {
  const { test } = await import('vitest')
  test.skip('select keyboard e2e is run via pnpm test:select-keyboard (Playwright)', () => {})
} else {
  const { test, expect } = await import('@playwright/test')

  const trigger = (page: Page) => page.locator('.select-trigger')
  const listbox = (page: Page) => page.locator('[role="listbox"]')
  const options = (page: Page) => page.locator('[role="option"]')
  const selected = (page: Page) => page.locator('[data-testid="selected"]')

  async function focusTrigger(page: Page) {
    await page.goto(HARNESS, { waitUntil: 'networkidle' })
    await trigger(page).focus()
    await expect(trigger(page)).toBeFocused()
  }

  test('opens with the keyboard and moves focus into the teleported listbox', async ({ page }: { page: Page }) => {
    await focusTrigger(page)

    // Native <button> — Enter activates it and opens the dropdown.
    await page.keyboard.press('Enter')
    await expect(listbox(page)).toBeVisible()

    // The regression: focus stays on the trigger and never enters the listbox,
    // so every ArrowUp/Down/Enter/Escape handler on the listbox is unreachable.
    await expect(listbox(page)).toBeFocused()
  })

  test('ArrowDown + Enter selects an option and closes the dropdown', async ({ page }: { page: Page }) => {
    await focusTrigger(page)
    await page.keyboard.press('Enter')
    await expect(listbox(page)).toBeFocused()

    // First ArrowDown activates the first enabled option (Apple), second → Banana.
    await page.keyboard.press('ArrowDown')
    await page.keyboard.press('ArrowDown')
    await expect(options(page).nth(1)).toHaveClass(/dropdown-item--active/)

    await page.keyboard.press('Enter')
    await expect(listbox(page)).toBeHidden()
    await expect(selected(page)).toHaveText('banana')
  })

  test('Escape closes the dropdown and returns focus to the trigger', async ({ page }: { page: Page }) => {
    await focusTrigger(page)
    await page.keyboard.press('Enter')
    await expect(listbox(page)).toBeFocused()

    await page.keyboard.press('Escape')
    await expect(listbox(page)).toBeHidden()
    await expect(trigger(page)).toBeFocused()
  })

  // ARIA semantics, not keyboard mechanics: the four cases above prove the
  // keyboard flow works, but a screen reader still could not tell that the
  // trigger is a combobox nor which list it owns. aria-controls is asserted
  // against a real element because a dangling id reads as "controls nothing".
  test('trigger carries combobox semantics wired to the listbox', async ({ page }: { page: Page }) => {
    await focusTrigger(page)

    const t = trigger(page)
    await expect(t).toHaveAttribute('role', 'combobox')
    await expect(t).toHaveAttribute('aria-haspopup', 'listbox')
    await expect(t).toHaveAttribute('aria-expanded', 'false')

    await page.keyboard.press('Enter')
    await expect(t).toHaveAttribute('aria-expanded', 'true')

    const controls = await t.getAttribute('aria-controls')
    expect(controls, 'trigger must declare aria-controls while open').toBeTruthy()
    const target = page.locator(`#${controls}`)
    await expect(target).toHaveCount(1)
    await expect(target).toHaveAttribute('role', 'listbox')

    // Closed again → the listbox leaves the DOM, so the attribute must go with
    // it rather than point at a removed node.
    await page.keyboard.press('Escape')
    await expect(listbox(page)).toBeHidden()
    await expect(t).not.toHaveAttribute('aria-controls', /.+/)
  })
}
