import { useLayoutEffect } from 'react'
import { afterEach, describe, expect, it, vi } from 'vitest'

import { fireEvent, renderWithProviders, screen } from '../../../test/utils'

import { VIEWER_ACTION_CLASS } from './viewerChromeClasses'
import ViewerShell from './ViewerShell'

const dialogPrototype = HTMLDialogElement.prototype
const originalDialogDescriptors = {
  close: Object.getOwnPropertyDescriptor(dialogPrototype, 'close'),
  showModal: Object.getOwnPropertyDescriptor(dialogPrototype, 'showModal'),
}

const defineDialogMethod = (
  name: 'close' | 'showModal',
  implementation: () => void
) => {
  Object.defineProperty(dialogPrototype, name, {
    configurable: true,
    value: implementation,
    writable: true,
  })
}

const restoreDialogMethod = (
  name: 'close' | 'showModal',
  descriptor: PropertyDescriptor | undefined
) => {
  if (descriptor) Object.defineProperty(dialogPrototype, name, descriptor)
  else Reflect.deleteProperty(dialogPrototype, name)
}

const PreOpenDialog = () => {
  useLayoutEffect(() => {
    document.querySelector('[data-testid="viewer"]')?.setAttribute('open', '')
  }, [])
  return null
}

afterEach(() => {
  restoreDialogMethod('close', originalDialogDescriptors.close)
  restoreDialogMethod('showModal', originalDialogDescriptors.showModal)
  vi.restoreAllMocks()
})

describe('ViewerShell', () => {
  it('drives open state, chrome content, and forwarded attributes', () => {
    const { rerender } = renderWithProviders(
      <ViewerShell
        open
        onClose={vi.fn()}
        counter="2 / 3"
        actions={<button type="button">Action</button>}
        ariaLabel="report.pdf"
        data-testid="viewer"
      >
        content
      </ViewerShell>
    )
    const dialog = screen.getByTestId('viewer')
    expect(dialog).toHaveAttribute('open')
    expect(dialog).toHaveAttribute('aria-label', 'report.pdf')
    expect(dialog).toHaveTextContent('2 / 3')
    expect(screen.getByRole('button', { name: 'Action' })).toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'Close viewer' })).toHaveClass(
      VIEWER_ACTION_CLASS
    )

    rerender(
      <ViewerShell open={false} onClose={vi.fn()} data-testid="viewer">
        content
      </ViewerShell>
    )
    expect(dialog).not.toHaveAttribute('open')
  })

  it('focuses close on open and restores the previous focus on close', () => {
    const onClose = vi.fn()
    const previouslyFocused = document.createElement('button')
    document.body.appendChild(previouslyFocused)
    previouslyFocused.focus()
    const { rerender } = renderWithProviders(
      <ViewerShell open={false} onClose={onClose} data-testid="viewer">
        content
      </ViewerShell>
    )
    rerender(
      <ViewerShell open onClose={onClose} data-testid="viewer">
        content
      </ViewerShell>
    )
    expect(screen.getByRole('button', { name: 'Close viewer' })).toHaveFocus()
    rerender(
      <ViewerShell open={false} onClose={onClose} data-testid="viewer">
        content
      </ViewerShell>
    )
    expect(previouslyFocused).toHaveFocus()
    previouslyFocused.remove()
  })

  it('falls back to the open attribute when dialog methods are absent', () => {
    Reflect.deleteProperty(dialogPrototype, 'showModal')
    Reflect.deleteProperty(dialogPrototype, 'close')
    const { rerender } = renderWithProviders(
      <ViewerShell open onClose={vi.fn()} data-testid="viewer">
        content
      </ViewerShell>
    )
    const dialog = screen.getByTestId('viewer')
    expect(dialog).toHaveAttribute('open')
    rerender(
      <ViewerShell open={false} onClose={vi.fn()} data-testid="viewer">
        content
      </ViewerShell>
    )
    expect(dialog).not.toHaveAttribute('open')
  })

  it('falls back when showModal throws', () => {
    const showModal = vi.fn(() => {
      throw new Error('not supported')
    })
    defineDialogMethod('showModal', showModal)
    const { container } = renderWithProviders(
      <ViewerShell open onClose={vi.fn()} data-testid="viewer">
        content
      </ViewerShell>
    )
    expect(container.querySelector('dialog')).toHaveAttribute('open')
    expect(showModal).toHaveBeenCalledOnce()
  })

  it('falls back when close throws during cleanup', () => {
    const close = vi.fn(() => {
      throw new Error('not supported')
    })
    defineDialogMethod('close', close)
    Reflect.deleteProperty(dialogPrototype, 'showModal')
    const { rerender } = renderWithProviders(
      <ViewerShell open onClose={vi.fn()} data-testid="viewer">
        content
      </ViewerShell>
    )
    const dialog = screen.getByTestId('viewer')
    rerender(
      <ViewerShell open={false} onClose={vi.fn()} data-testid="viewer">
        content
      </ViewerShell>
    )
    expect(close).toHaveBeenCalledOnce()
    expect(dialog).not.toHaveAttribute('open')
  })

  it('falls back when close throws in the closed branch', () => {
    const close = vi.fn(() => {
      throw new Error('not supported')
    })
    defineDialogMethod('close', close)
    renderWithProviders(
      <ViewerShell open={false} onClose={vi.fn()} data-testid="viewer">
        <PreOpenDialog />
      </ViewerShell>
    )
    expect(close).toHaveBeenCalledOnce()
    expect(screen.getByTestId('viewer')).not.toHaveAttribute('open')
  })

  it('does not call showModal when the dialog is already open', () => {
    const showModal = vi.fn()
    defineDialogMethod('showModal', showModal)
    renderWithProviders(
      <ViewerShell open onClose={vi.fn()} data-testid="viewer">
        <PreOpenDialog />
      </ViewerShell>
    )
    expect(screen.getByTestId('viewer')).toHaveAttribute('open')
    expect(showModal).not.toHaveBeenCalled()
  })

  it('does not restore focus to a detached element', () => {
    const previouslyFocused = document.createElement('button')
    document.body.appendChild(previouslyFocused)
    previouslyFocused.focus()
    const { rerender } = renderWithProviders(
      <ViewerShell open={false} onClose={vi.fn()} data-testid="viewer">
        content
      </ViewerShell>
    )
    rerender(
      <ViewerShell open onClose={vi.fn()} data-testid="viewer">
        content
      </ViewerShell>
    )
    previouslyFocused.remove()
    expect(() =>
      rerender(
        <ViewerShell open={false} onClose={vi.fn()} data-testid="viewer">
          content
        </ViewerShell>
      )
    ).not.toThrow()
    expect(document.activeElement).not.toBe(previouslyFocused)
  })

  it('uses the default accessible label', () => {
    renderWithProviders(
      <ViewerShell open onClose={vi.fn()} data-testid="viewer">
        content
      </ViewerShell>
    )
    expect(screen.getByTestId('viewer')).toHaveAttribute(
      'aria-label',
      'Attachment viewer'
    )
  })

  it('does not close a dialog that was never opened', () => {
    const close = vi.fn()
    defineDialogMethod('close', close)
    renderWithProviders(
      <ViewerShell open={false} onClose={vi.fn()} data-testid="viewer">
        content
      </ViewerShell>
    )
    expect(close).not.toHaveBeenCalled()
  })

  it('closes from the close, native close, and backdrop events', () => {
    const onClose = vi.fn()
    renderWithProviders(
      <ViewerShell open onClose={onClose} data-testid="viewer">
        <span>content</span>
      </ViewerShell>
    )
    const dialog = screen.getByTestId('viewer')
    fireEvent.click(screen.getByRole('button', { name: 'Close viewer' }))
    fireEvent(dialog, new Event('close'))
    fireEvent.click(dialog)
    expect(onClose).toHaveBeenCalledTimes(3)
    onClose.mockClear()
    fireEvent.click(screen.getByText('content'))
    expect(onClose).not.toHaveBeenCalled()
  })
})
