import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { nextTick } from 'vue'
import StorageBrowser from '../StorageBrowser.vue'

const globalStubs = {
  BreadcrumbBar: true,
  SearchBar: true,
  SortControls: true,
  DropZone: true,
  UploadProgress: true,
  TopicBadge: true,
  AppIcon: true,
  EmptyState: true,
}

describe('UX-02: StorageBrowser shows skeleton rows during loading', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })

  it('renders 5 skeleton rows when loading=true and lists empty', () => {
    const wrapper = mount(StorageBrowser, {
      props: { loading: true, folders: [], files: [] },
      global: { stubs: globalStubs },
    })
    const pulseEls = wrapper.findAll('.animate-pulse')
    expect(pulseEls.length).toBeGreaterThanOrEqual(5)
  })

  it('Loading… text is absent when loading=true', () => {
    const wrapper = mount(StorageBrowser, {
      props: { loading: true, folders: [], files: [] },
      global: { stubs: globalStubs },
    })
    expect(wrapper.text()).not.toContain('Loading…')
  })

  it('skeleton rows are NOT rendered when loading=false', () => {
    const wrapper = mount(StorageBrowser, {
      props: { loading: false, folders: [], files: [] },
      global: { stubs: globalStubs },
    })
    const pulseEls = wrapper.findAll('.animate-pulse')
    expect(pulseEls.length).toBe(0)
  })

  it('skeleton row has mobile base icon/name/actions grid class', () => {
    const wrapper = mount(StorageBrowser, {
      props: { loading: true, folders: [], files: [] },
      global: { stubs: globalStubs },
    })
    const skeletonGrids = wrapper.findAll('.grid-cols-\\[2rem_minmax\\(0\\,1fr\\)_7rem\\]')
    expect(skeletonGrids.length).toBeGreaterThanOrEqual(1)
  })

  it('skeleton row has md breakpoint full grid class', () => {
    const wrapper = mount(StorageBrowser, {
      props: { loading: true, folders: [], files: [] },
      global: { stubs: globalStubs },
    })
    const skeletonGrids = wrapper.findAll('.md\\:grid-cols-\\[2rem_minmax\\(0\\,1fr\\)_6rem_8rem_7rem\\]')
    expect(skeletonGrids.length).toBeGreaterThanOrEqual(1)
  })
})

const SAMPLE_FILE = {
  id: 'file-1',
  original_name: 'doc.pdf',
  size_bytes: 2048,
  created_at: '2025-01-01T00:00:00Z',
  status: 'ready',
  topics: [],
  is_shared: false,
}

const SAMPLE_FOLDER = { id: 'folder-1', name: 'Archive', doc_count: 0 }

describe('RESP-02/RESP-03: StorageBrowser responsive column classes and touch targets', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })

  it('list header has mobile base icon/name/actions grid class', () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [SAMPLE_FOLDER], files: [], loading: false },
      global: { stubs: globalStubs },
    })
    const header = wrapper.find('[data-test="list-header"]')
    expect(header.exists()).toBe(true)
    expect(header.classes()).toContain('grid-cols-[2rem_minmax(0,1fr)_7rem]')
  })

  it('list header has md breakpoint full-column class', () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [SAMPLE_FOLDER], files: [], loading: false },
      global: { stubs: globalStubs },
    })
    const header = wrapper.find('[data-test="list-header"]')
    expect(header.classes()).toContain('md:grid-cols-[2rem_minmax(0,1fr)_6rem_8rem_7rem]')
  })

  it('folder row has mobile base icon/name/actions grid class', () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [SAMPLE_FOLDER], files: [], loading: false },
      global: { stubs: globalStubs },
    })
    const folderRows = wrapper.findAll('.grid-cols-\\[2rem_minmax\\(0\\,1fr\\)_7rem\\]')
    // At least the folder row and header exist
    expect(folderRows.length).toBeGreaterThanOrEqual(1)
  })

  it('file row has mobile base icon/name/actions grid class', () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [], files: [SAMPLE_FILE], loading: false },
      global: { stubs: globalStubs },
    })
    const fileRows = wrapper.findAll('.grid-cols-\\[2rem_minmax\\(0\\,1fr\\)_7rem\\]')
    expect(fileRows.length).toBeGreaterThanOrEqual(1)
  })

  it('size column is hidden below md (has hidden md:block classes)', () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [], files: [SAMPLE_FILE], loading: false },
      global: { stubs: globalStubs },
    })
    const sizeSpans = wrapper.findAll('.hidden.md\\:block')
    expect(sizeSpans.length).toBeGreaterThanOrEqual(1)
  })

  it('modified column is hidden below sm (has hidden sm:block classes)', () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [], files: [SAMPLE_FILE], loading: false },
      global: { stubs: globalStubs },
    })
    const modifiedSpans = wrapper.findAll('.hidden.sm\\:block')
    expect(modifiedSpans.length).toBeGreaterThanOrEqual(1)
  })

  it('file action buttons have min-w-[36px] touch target class', () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [], files: [SAMPLE_FILE], loading: false },
      global: { stubs: globalStubs },
    })
    const actionBtns = wrapper.findAll('button[title="Delete"], button[title="Share"]')
    expect(actionBtns.length).toBeGreaterThanOrEqual(1)
    for (const btn of actionBtns) {
      expect(btn.classes()).toContain('min-w-[36px]')
      expect(btn.classes()).toContain('min-h-[36px]')
    }
  })

  it('folder action buttons have min-w-[36px] touch target class', () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [SAMPLE_FOLDER], files: [], loading: false },
      global: { stubs: globalStubs },
    })
    const renameBtns = wrapper.findAll('button[title="Rename"]')
    expect(renameBtns.length).toBeGreaterThanOrEqual(1)
    for (const btn of renameBtns) {
      expect(btn.classes()).toContain('min-w-[36px]')
      expect(btn.classes()).toContain('min-h-[36px]')
    }
  })

  it('file row actions are visible without hover-only opacity classes', () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [], files: [SAMPLE_FILE], loading: false },
      global: { stubs: globalStubs },
    })
    const actions = wrapper.find('[data-test="file-row-actions"]')
    expect(actions.exists()).toBe(true)
    expect(actions.classes()).not.toContain('opacity-0')
    expect(actions.classes()).not.toContain('group-hover:opacity-100')
  })

  it('folder row actions are visible without hover-only opacity classes', () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [SAMPLE_FOLDER], files: [], loading: false },
      global: { stubs: globalStubs },
    })
    const actions = wrapper.find('[data-test="folder-row-actions"]')
    expect(actions.exists()).toBe(true)
    expect(actions.classes()).not.toContain('opacity-0')
    expect(actions.classes()).not.toContain('group-hover:opacity-100')
  })
})

describe('Plan 11-07: StorageBrowser compact mobile toolbar', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })

  it('renders icon-only mobile controls and hides full toolbar below sm', () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [], files: [], loading: false },
      global: { stubs: globalStubs },
    })

    const mobileToolbar = wrapper.find('[data-test="mobile-toolbar"]')
    const desktopToolbar = wrapper.find('[data-test="desktop-toolbar"]')
    expect(mobileToolbar.exists()).toBe(true)
    expect(mobileToolbar.classes()).toContain('sm:hidden')
    expect(desktopToolbar.exists()).toBe(true)
    expect(desktopToolbar.classes()).toContain('hidden')
    expect(desktopToolbar.classes()).toContain('sm:flex')
    expect(wrapper.find('button[aria-label="Search"]').exists()).toBe(true)
    expect(wrapper.find('button[aria-label="Sort"]').exists()).toBe(true)
    expect(wrapper.find('button[aria-label="New folder"]').exists()).toBe(true)
  })

  it('search icon toggles a full-width mobile search row', async () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [], files: [], loading: false },
      global: { stubs: globalStubs },
    })

    expect(wrapper.find('[data-test="mobile-search-row"]').exists()).toBe(false)
    await wrapper.find('button[aria-label="Search"]').trigger('click')
    await nextTick()
    const row = wrapper.find('[data-test="mobile-search-row"]')
    expect(row.exists()).toBe(true)
    expect(row.classes()).toContain('w-full')
  })

  it('sort icon opens mobile sort options and emits sort-change', async () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [], files: [], loading: false },
      global: { stubs: { ...globalStubs, SortControls: false } },
    })

    await wrapper.find('button[aria-label="Sort"]').trigger('click')
    await nextTick()
    const panel = wrapper.find('[data-test="mobile-sort-panel"]')
    expect(panel.exists()).toBe(true)
    await panel.findAll('button').find(btn => btn.text().includes('Name')).trigger('click')

    expect(wrapper.emitted('sort-change')).toEqual([[{ sort: 'name', order: 'desc' }]])
    expect(wrapper.find('[data-test="mobile-sort-panel"]').exists()).toBe(false)
  })

  it('new-folder icon emits the existing new-folder event', async () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', folders: [], files: [], loading: false },
      global: { stubs: globalStubs },
    })

    await wrapper.find('button[aria-label="New folder"]').trigger('click')
    expect(wrapper.emitted('new-folder')).toHaveLength(1)
  })
})

describe('UX-13: StorageBrowser folder picker uses Teleport + getBoundingClientRect', () => {
  beforeEach(() => {
    vi.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue({
      top: 100, bottom: 140, left: 50, right: 250,
      width: 200, height: 40, x: 50, y: 100,
    })
    Object.defineProperty(window, 'innerHeight', { value: 800, writable: true })
  })

  afterEach(() => {
    vi.restoreAllMocks()
  })

  it('folder picker dropdown is teleported to body (escapes overflow container)', async () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', files: [SAMPLE_FILE], folders: [], rootFolders: [SAMPLE_FOLDER] },
      global: { stubs: globalStubs },
      attachTo: document.body,
    })
    const moveBtn = wrapper.find('button[title="Move to folder"]')
    expect(moveBtn.exists()).toBe(true)
    await moveBtn.trigger('click')
    await nextTick()
    const picker = document.body.querySelector('[data-test="folder-picker"]')
    expect(picker).not.toBeNull()
    wrapper.unmount()
  })

  it('dropdown position reflects getBoundingClientRect of the trigger button', async () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', files: [SAMPLE_FILE], folders: [], rootFolders: [SAMPLE_FOLDER] },
      global: { stubs: globalStubs },
      attachTo: document.body,
    })
    const moveBtn = wrapper.find('button[title="Move to folder"]')
    await moveBtn.trigger('click')
    await nextTick()
    const picker = document.body.querySelector('[data-test="folder-picker"]')
    expect(picker).not.toBeNull()
    const style = picker.style
    expect(style.top || style.bottom).toBeTruthy()
    expect(style.left).toBeTruthy()
    wrapper.unmount()
  })

  it('window scroll while open recalculates position', async () => {
    const wrapper = mount(StorageBrowser, {
      props: { mode: 'local', files: [SAMPLE_FILE], folders: [], rootFolders: [SAMPLE_FOLDER] },
      global: { stubs: globalStubs },
      attachTo: document.body,
    })
    const moveBtn = wrapper.find('button[title="Move to folder"]')
    await moveBtn.trigger('click')
    await nextTick()
    const picker = document.body.querySelector('[data-test="folder-picker"]')
    const initialTop = picker.style.top || picker.style.bottom
    vi.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue({
      top: 300, bottom: 340, left: 50, right: 250,
      width: 200, height: 40, x: 50, y: 300,
    })
    window.dispatchEvent(new Event('scroll', { bubbles: true }))
    await nextTick()
    const updatedTop = picker.style.top || picker.style.bottom
    expect(updatedTop).not.toBe(initialTop)
    wrapper.unmount()
  })
})
