import { describe, it, expect, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
import BaseBottomSheet from '@/components/BaseBottomSheet.vue'

describe('BaseBottomSheet', () => {
  beforeEach(() => {
    document.body.innerHTML = ''
  })

  it('does not render the overlay when modelValue is false', async () => {
    mount(BaseBottomSheet, {
      props: { modelValue: false, label: 'Test' },
      slots: { default: '<p class="content">hi</p>' },
      attachTo: document.body,
    })
    await new Promise(r => setTimeout(r, 0))
    expect(document.querySelector('.sheet-overlay')).toBeNull()
  })

  it('renders the sheet teleported to body when open', async () => {
    mount(BaseBottomSheet, {
      props: { modelValue: true, label: 'Frame settings' },
      slots: { default: '<p class="content">hi</p>' },
      attachTo: document.body,
    })
    await new Promise(r => setTimeout(r, 0))
    const overlay = document.querySelector('.sheet-overlay')
    expect(overlay).not.toBeNull()
    expect(overlay?.getAttribute('aria-label')).toBe('Frame settings')
    expect(document.querySelector('.sheet .content')).not.toBeNull()
  })

  it('emits update:modelValue=false when the overlay is clicked', async () => {
    const wrapper = mount(BaseBottomSheet, {
      props: { modelValue: true, label: 'X' },
      attachTo: document.body,
    })
    await new Promise(r => setTimeout(r, 0))
    const overlay = document.querySelector('.sheet-overlay') as HTMLElement
    overlay.click()
    expect(wrapper.emitted('update:modelValue')).toEqual([[false]])
  })

  it('emits update:modelValue=false on Escape', async () => {
    const wrapper = mount(BaseBottomSheet, {
      props: { modelValue: true, label: 'X' },
      attachTo: document.body,
    })
    await new Promise(r => setTimeout(r, 0))
    const overlay = document.querySelector('.sheet-overlay') as HTMLElement
    overlay.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
    expect(wrapper.emitted('update:modelValue')).toEqual([[false]])
  })

  it('emits update:modelValue=false when the handle is dragged down past the threshold', async () => {
    const wrapper = mount(BaseBottomSheet, {
      props: { modelValue: true, label: 'X' },
      attachTo: document.body,
    })
    await new Promise(r => setTimeout(r, 0))
    const handle = document.querySelector('.sheet__handle-target') as HTMLElement
    function touchEvent(type: string, y: number) {
      const e = new Event(type, { bubbles: true }) as TouchEvent
      Object.defineProperty(e, 'touches', { value: [{ clientY: y }] })
      Object.defineProperty(e, 'changedTouches', { value: [{ clientY: y }] })
      return e
    }
    handle.dispatchEvent(touchEvent('touchstart', 100))
    handle.dispatchEvent(touchEvent('touchmove',  220))
    handle.dispatchEvent(touchEvent('touchend',   220))
    expect(wrapper.emitted('update:modelValue')).toEqual([[false]])
  })

  it('closes via pointer drag (mouse / desktop) past the threshold', async () => {
    const wrapper = mount(BaseBottomSheet, {
      props: { modelValue: true, label: 'X' },
      attachTo: document.body,
    })
    await new Promise(r => setTimeout(r, 0))
    const handle = document.querySelector('.sheet__handle-target') as HTMLElement
    ;(handle as any).setPointerCapture = () => {}
    function pointerEvent(type: string, y: number, id = 1, pointerType = 'mouse') {
      const e = new Event(type, { bubbles: true }) as PointerEvent
      Object.defineProperty(e, 'clientY', { value: y })
      Object.defineProperty(e, 'pointerId', { value: id })
      Object.defineProperty(e, 'pointerType', { value: pointerType })
      return e
    }
    handle.dispatchEvent(pointerEvent('pointerdown', 50))
    window.dispatchEvent(pointerEvent('pointermove', 200))
    window.dispatchEvent(pointerEvent('pointerup',   200))
    expect(wrapper.emitted('update:modelValue')).toEqual([[false]])
  })

  it('ignores pointerdown for touch pointers (touch handlers cover it)', async () => {
    const wrapper = mount(BaseBottomSheet, {
      props: { modelValue: true, label: 'X' },
      attachTo: document.body,
    })
    await new Promise(r => setTimeout(r, 0))
    const handle = document.querySelector('.sheet__handle-target') as HTMLElement
    ;(handle as any).setPointerCapture = () => {}
    function pointerEvent(type: string, y: number) {
      const e = new Event(type, { bubbles: true }) as PointerEvent
      Object.defineProperty(e, 'clientY', { value: y })
      Object.defineProperty(e, 'pointerId', { value: 1 })
      Object.defineProperty(e, 'pointerType', { value: 'touch' })
      return e
    }
    handle.dispatchEvent(pointerEvent('pointerdown', 50))
    window.dispatchEvent(pointerEvent('pointermove', 200))
    window.dispatchEvent(pointerEvent('pointerup',   200))
    expect(wrapper.emitted('update:modelValue')).toBeUndefined()
  })

  it('does not close when the handle is dragged less than the dismiss threshold', async () => {
    const wrapper = mount(BaseBottomSheet, {
      props: { modelValue: true, label: 'X' },
      attachTo: document.body,
    })
    await new Promise(r => setTimeout(r, 0))
    const handle = document.querySelector('.sheet__handle-target') as HTMLElement
    function touchEvent(type: string, y: number) {
      const e = new Event(type, { bubbles: true }) as TouchEvent
      Object.defineProperty(e, 'touches', { value: [{ clientY: y }] })
      return e
    }
    handle.dispatchEvent(touchEvent('touchstart', 100))
    handle.dispatchEvent(touchEvent('touchmove',  150)) // 50px — below 80 threshold
    handle.dispatchEvent(touchEvent('touchend',   150))
    expect(wrapper.emitted('update:modelValue')).toBeUndefined()
  })

  it('focuses the sheet when opened and restores focus to the trigger when closed', async () => {
    const trigger = document.createElement('button')
    document.body.appendChild(trigger)
    trigger.focus()

    const wrapper = mount(BaseBottomSheet, {
      props: { modelValue: false, label: 'X' },
      attachTo: document.body,
    })
    await wrapper.setProps({ modelValue: true })
    await new Promise(r => setTimeout(r, 0))
    const sheet = document.querySelector('.sheet') as HTMLElement
    expect(document.activeElement).toBe(sheet)

    await wrapper.setProps({ modelValue: false })
    await new Promise(r => setTimeout(r, 0))
    expect(document.activeElement).toBe(trigger)
  })
})
