import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { mount } from '@vue/test-utils'
import OsDragOverlay from '../OsDragOverlay.vue'

function makeDragEvent(type, types = ['Files'], files = []) {
  const event = new Event(type, { bubbles: true, cancelable: true })
  Object.defineProperty(event, 'dataTransfer', {
    value: { types, files, preventDefault: vi.fn() },
    writable: false,
  })
  event.preventDefault = vi.fn()
  return event
}

describe('UX-09: OsDragOverlay shows when OS files are dragged over the window', () => {
  it('overlay hidden by default (dragDepth=0)', () => {
    const w = mount(OsDragOverlay, {
      attachTo: document.body,
      global: { stubs: { Teleport: true, AppIcon: true } },
    })
    expect(w.vm.showOverlay).toBe(false)
    expect(w.vm.dragDepth).toBe(0)
    w.unmount()
  })

  it('dragenter with dataTransfer.types including "Files" shows overlay (dragDepth=1)', async () => {
    const w = mount(OsDragOverlay, {
      attachTo: document.body,
      global: { stubs: { Teleport: true, AppIcon: true } },
    })
    const event = makeDragEvent('dragenter', ['Files'])
    window.dispatchEvent(event)
    await w.vm.$nextTick()
    expect(w.vm.showOverlay).toBe(true)
    expect(w.vm.dragDepth).toBe(1)
    w.unmount()
  })

  it('dragenter without "Files" type is ignored (in-app element drag)', async () => {
    const w = mount(OsDragOverlay, {
      attachTo: document.body,
      global: { stubs: { Teleport: true, AppIcon: true } },
    })
    const event = makeDragEvent('dragenter', ['text/plain'])
    window.dispatchEvent(event)
    await w.vm.$nextTick()
    expect(w.vm.showOverlay).toBe(false)
    expect(w.vm.dragDepth).toBe(0)
    w.unmount()
  })

  it('dragleave decrements depth; overlay hides when depth reaches 0', async () => {
    const w = mount(OsDragOverlay, {
      attachTo: document.body,
      global: { stubs: { Teleport: true, AppIcon: true } },
    })
    window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
    await w.vm.$nextTick()
    expect(w.vm.dragDepth).toBe(1)
    expect(w.vm.showOverlay).toBe(true)

    window.dispatchEvent(new Event('dragleave'))
    await w.vm.$nextTick()
    expect(w.vm.dragDepth).toBe(0)
    expect(w.vm.showOverlay).toBe(false)
    w.unmount()
  })

  it('nested dragenter+dragleave maintains overlay until depth=0', async () => {
    const w = mount(OsDragOverlay, {
      attachTo: document.body,
      global: { stubs: { Teleport: true, AppIcon: true } },
    })
    window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
    window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
    await w.vm.$nextTick()
    expect(w.vm.dragDepth).toBe(2)
    expect(w.vm.showOverlay).toBe(true)

    window.dispatchEvent(new Event('dragleave'))
    await w.vm.$nextTick()
    expect(w.vm.dragDepth).toBe(1)
    expect(w.vm.showOverlay).toBe(true)

    window.dispatchEvent(new Event('dragleave'))
    await w.vm.$nextTick()
    expect(w.vm.dragDepth).toBe(0)
    expect(w.vm.showOverlay).toBe(false)
    w.unmount()
  })

  it('drop event emits files-dropped with the file list', async () => {
    const w = mount(OsDragOverlay, {
      attachTo: document.body,
      global: { stubs: { Teleport: true, AppIcon: true } },
    })
    window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
    await w.vm.$nextTick()

    const file = new File(['x'], 'a.txt', { type: 'text/plain' })
    const dropEvent = new Event('drop', { bubbles: true, cancelable: true })
    Object.defineProperty(dropEvent, 'dataTransfer', {
      value: { files: [file] },
      writable: false,
    })
    dropEvent.preventDefault = vi.fn()
    window.dispatchEvent(dropEvent)
    await w.vm.$nextTick()

    const emitted = w.emitted('files-dropped')
    expect(emitted).toBeTruthy()
    expect(emitted[0][0]).toHaveLength(1)
    expect(emitted[0][0][0]).toBeInstanceOf(File)
    w.unmount()
  })

  it('drop resets dragDepth to 0 and hides overlay', async () => {
    const w = mount(OsDragOverlay, {
      attachTo: document.body,
      global: { stubs: { Teleport: true, AppIcon: true } },
    })
    window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
    window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
    window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
    await w.vm.$nextTick()
    expect(w.vm.dragDepth).toBe(3)

    const dropEvent = new Event('drop', { bubbles: true, cancelable: true })
    Object.defineProperty(dropEvent, 'dataTransfer', {
      value: { files: [] },
      writable: false,
    })
    dropEvent.preventDefault = vi.fn()
    window.dispatchEvent(dropEvent)
    await w.vm.$nextTick()

    expect(w.vm.dragDepth).toBe(0)
    expect(w.vm.showOverlay).toBe(false)

    window.dispatchEvent(new Event('dragleave'))
    await w.vm.$nextTick()
    expect(w.vm.dragDepth).toBe(0)
    w.unmount()
  })

  it('overlay is teleported to body with z-[9998] (below toast z-[9999])', async () => {
    const w = mount(OsDragOverlay, {
      attachTo: document.body,
    })
    window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
    await w.vm.$nextTick()

    const overlay = document.querySelector('[data-test="os-drag-overlay"]')
    expect(overlay).toBeTruthy()
    expect(overlay.classList.contains('z-[9998]')).toBe(true)
    w.unmount()
  })
})
