import { describe, it, expect, vi } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { nextTick } from 'vue'
import TreeItem from '../TreeItem.vue'

vi.mock('../AppIcon.vue', () => ({ default: { template: '<span/>' } }))

function mountItem(overrides = {}) {
  return mount(TreeItem, {
    props: {
      label: 'Test',
      loadChildren: vi.fn().mockResolvedValue([]),
      expandable: true,
      ...overrides,
    },
    global: { stubs: { RouterLink: true } },
  })
}

describe('Gap 1: sidebar shimmer rows', () => {
  it('renders animate-pulse elements when loading=true (expanded via refresh)', async () => {
    // First expand the item (so expanded=true, childrenLoaded=true)
    let resolveFn1
    const loadChildren = vi.fn()
      .mockReturnValueOnce(Promise.resolve([]))  // first load resolves immediately
      .mockReturnValue(new Promise(resolve => { resolveFn1 = resolve }))  // refresh never resolves
    const w = mountItem({ loadChildren })
    // Expand by awaiting toggleExpand — sets expanded=true after load completes
    const setupState = w.vm.$.setupState
    await setupState.toggleExpand()
    await flushPromises()
    // Now call refresh() without awaiting — it calls load() which sets loading=true while expanded stays true
    setupState.refresh()  // NOT awaited — loading=true while promise is pending
    await nextTick()  // let Vue update the DOM
    const pulseEls = w.findAll('.animate-pulse')
    expect(pulseEls.length).toBeGreaterThanOrEqual(2)
    // Cleanup
    if (resolveFn1) resolveFn1([])
    await flushPromises()
  })

  it('does not render "Loading" text when loading=true (expanded via refresh)', async () => {
    let resolveFn1
    const loadChildren = vi.fn()
      .mockReturnValueOnce(Promise.resolve([]))
      .mockReturnValue(new Promise(resolve => { resolveFn1 = resolve }))
    const w = mountItem({ loadChildren })
    const setupState = w.vm.$.setupState
    await setupState.toggleExpand()
    await flushPromises()
    setupState.refresh()
    await nextTick()
    expect(w.text()).not.toContain('Loading')
    if (resolveFn1) resolveFn1([])
    await flushPromises()
  })
})

describe('Gap 1 regression: non-loading branches unchanged', () => {
  it('shows Empty text when loadChildren resolves to [] and item is expanded', async () => {
    const w = mountItem({ loadChildren: vi.fn().mockResolvedValue([]) })
    const toggleExpand = w.vm.$.setupState.toggleExpand
    await toggleExpand()
    await flushPromises()
    await nextTick()
    expect(w.text()).toContain('Empty')
  })
})

describe('Phase 11 validation: TreeItem visual invariants', () => {
  it('applies focus-visible rings to the expand control and selectable row button', () => {
    const w = mountItem()
    const buttons = w.findAll('button')

    expect(buttons).toHaveLength(2)
    for (const button of buttons) {
      const cls = button.attributes('class') || ''
      expect(cls).toContain('focus-visible:outline-none')
      expect(cls).toContain('focus-visible:ring-2')
      expect(cls).toContain('focus-visible:ring-indigo-500')
      expect(cls).toContain('focus-visible:ring-offset-1')
    }
  })

  it('applies focus-visible rings to the router-link row', () => {
    const w = mountItem({ to: '/folders/archive' })
    const link = w.findComponent({ name: 'RouterLink' })

    expect(link.exists()).toBe(true)
    const cls = link.attributes('class') || ''
    expect(cls).toContain('focus-visible:outline-none')
    expect(cls).toContain('focus-visible:ring-2')
    expect(cls).toContain('focus-visible:ring-indigo-500')
    expect(cls).toContain('focus-visible:ring-offset-1')
  })

  it('uses Tailwind width classes for loading skeleton labels without inline width styles', async () => {
    let resolveFn
    const loadChildren = vi.fn()
      .mockReturnValueOnce(Promise.resolve([]))
      .mockReturnValue(new Promise(resolve => { resolveFn = resolve }))
    const w = mountItem({ loadChildren })

    await w.vm.$.setupState.toggleExpand()
    await flushPromises()
    w.vm.$.setupState.refresh()
    await nextTick()

    const skeletonLabels = w.findAll('.h-3.animate-pulse')
    expect(skeletonLabels).toHaveLength(3)
    expect(skeletonLabels.map(el => el.classes().find(cls => /^w-\d+$/.test(cls)))).toEqual([
      'w-12',
      'w-16',
      'w-20',
    ])

    for (const label of skeletonLabels) {
      expect(label.attributes('style') || '').not.toMatch(/width\s*:/)
    }

    resolveFn([])
    await flushPromises()
  })
})
