// @vitest-environment happy-dom
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { act, renderHook } from '@testing-library/react'

import {
  hasBlockingNavigation,
  NAVIGATION_CONFIRM_MESSAGE,
  registerNavigationBlocker,
  useAdminRouter,
  useNavigationBlocker,
} from '../../router/index.js'

// Guards against silent data loss: an in-app route change (Cancel, sidebar /
// breadcrumb links, command palette) must confirm before discarding an editor's
// unsaved changes — beforeunload only covers hard reloads, never SPA navigation.
describe('navigation blocker', () => {
  let confirmMock: ReturnType<typeof vi.fn>

  beforeEach(() => {
    // Reset the URL so each test's router mounts at '/' (pushState from a prior
    // test would otherwise leak into window.location and skew currentPath).
    window.history.replaceState(null, '', '/admin')
    confirmMock = vi.fn(() => true)
    ;(window as unknown as { confirm: typeof window.confirm }).confirm =
      confirmMock as unknown as typeof window.confirm
  })

  afterEach(() => {
    // Ensure no blocker leaks into the next test.
    expect(hasBlockingNavigation()).toBe(false)
  })

  it('navigates freely and never prompts when nothing is dirty', () => {
    const { result } = renderHook(() => useAdminRouter('/admin', '/'))
    act(() => result.current.navigate('/collections/posts'))
    expect(result.current.currentPath).toBe('/collections/posts')
    expect(confirmMock).not.toHaveBeenCalled()
  })

  it('aborts the navigation when a dirty editor is registered and the user cancels', () => {
    confirmMock.mockReturnValue(false)
    const unregister = registerNavigationBlocker(() => true)
    try {
      const { result } = renderHook(() => useAdminRouter('/admin', '/'))
      act(() => result.current.navigate('/collections/posts'))
      expect(confirmMock).toHaveBeenCalledWith(NAVIGATION_CONFIRM_MESSAGE)
      expect(result.current.currentPath).toBe('/')
    } finally {
      unregister()
    }
  })

  it('proceeds with the navigation when the user confirms leaving', () => {
    confirmMock.mockReturnValue(true)
    const unregister = registerNavigationBlocker(() => true)
    try {
      const { result } = renderHook(() => useAdminRouter('/admin', '/'))
      act(() => result.current.navigate('/settings'))
      expect(confirmMock).toHaveBeenCalledOnce()
      expect(result.current.currentPath).toBe('/settings')
    } finally {
      unregister()
    }
  })

  it('useNavigationBlocker only blocks while dirty and cleans up on unmount', () => {
    const { rerender, unmount } = renderHook(({ dirty }) => useNavigationBlocker(dirty), {
      initialProps: { dirty: false },
    })
    expect(hasBlockingNavigation()).toBe(false)
    rerender({ dirty: true })
    expect(hasBlockingNavigation()).toBe(true)
    rerender({ dirty: false })
    expect(hasBlockingNavigation()).toBe(false)
    rerender({ dirty: true })
    expect(hasBlockingNavigation()).toBe(true)
    unmount()
    expect(hasBlockingNavigation()).toBe(false)
  })
})
