// @vitest-environment jsdom
//
// The admin shell + AUTH GATE. The loader redirects an unauthenticated request
// and otherwise returns the shared user. The rendered shell derives its nav
// from the api capability map (useCapabilityManifest + deriveEntityAdmins) and
// wraps an undo/redo bar (useUndoLog). We mock @voltro/client so those hooks are
// deterministic, mock the framework hooks, and import the REAL RedirectError.

import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { act, createElement, type ReactNode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { RedirectError } from '@voltro/web'
import { I18nProvider } from '@voltro/i18n'
import enCatalog from '../../locales/en'

const loaderData = vi.fn<() => unknown>()
const location = vi.fn<() => string>()

vi.mock('@voltro/web', async () => {
  const actual = await vi.importActual<typeof import('@voltro/web')>('@voltro/web')
  return { ...actual, useLoaderData: () => loaderData(), useLocation: () => location() }
})

interface ManifestState { manifest: unknown; loading: boolean }
const capabilityManifest = vi.fn<() => ManifestState>()
const entityAdmins = vi.fn<() => Array<{ table: string }>>()
const undoLog = vi.fn(() => ({
  canUndo: false,
  canRedo: false,
  undoLast: vi.fn(),
  redoLast: vi.fn(),
}))

vi.mock('@voltro/client', () => ({
  useCapabilityManifest: () => capabilityManifest(),
  deriveEntityAdmins: () => entityAdmins(),
  useUndoLog: () => undoLog(),
  PermissionProvider: ({ children }: { children?: ReactNode }) => children,
}))

const { default: AdminLayout, loader } = await import('./layout')

;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true

let container: HTMLDivElement
let root: Root

// AdminLayout uses <T>/useLocale + <LocaleSwitcher>, so wrap renders in the
// framework's <I18nProvider> (auto-wired in the running app).
const render = (node: ReactNode): void => {
  container = document.createElement('div')
  document.body.appendChild(container)
  act(() => {
    root = createRoot(container)
    root.render(
      createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
    )
  })
}

beforeEach(() => {
  loaderData.mockReset()
  location.mockReset()
  capabilityManifest.mockReset()
  entityAdmins.mockReset()
  undoLog.mockClear()
})

afterEach(() => {
  if (root) act(() => root.unmount())
  container?.remove()
  document.body.innerHTML = ''
})

describe('admin layout — loader (auth gate)', () => {
  test('redirects to /login (remembering the destination) when unauthenticated', async () => {
    const err = await Promise.resolve(loader({ headers: {} } as never)).catch((e: unknown) => e)
    expect(err).toBeInstanceOf(RedirectError)
    expect((err as RedirectError).location).toBe('/login?from=/admin')
  })

  test('returns the shared user for a signed-in request', async () => {
    const data = await loader({ headers: { cookie: 'demo_session=ok' } } as never)
    expect(data.user.name).toBe('Demo Admin')
  })
})

describe('admin layout — render', () => {
  test('renders a nav item per discovered entity plus the Overview link', () => {
    loaderData.mockReturnValue({ user: { name: 'Root Admin' } })
    location.mockReturnValue('/admin')
    capabilityManifest.mockReturnValue({ manifest: {}, loading: false })
    entityAdmins.mockReturnValue([{ table: 'todos' }, { table: 'projects' }])

    render(createElement(AdminLayout, { children: createElement('p', null, 'admin body') }))

    expect(container.textContent).toContain('Root Admin')
    const hrefs = Array.from(container.querySelectorAll('a')).map((a) => a.getAttribute('href'))
    expect(hrefs).toContain('/admin')
    expect(hrefs).toContain('/admin/todos')
    expect(hrefs).toContain('/admin/projects')
    expect(container.querySelector('main')!.textContent).toContain('admin body')
  })

  test('shows the discovering hint before the manifest arrives', () => {
    loaderData.mockReturnValue({ user: { name: 'Root Admin' } })
    location.mockReturnValue('/admin')
    capabilityManifest.mockReturnValue({ manifest: undefined, loading: true })
    entityAdmins.mockReturnValue([])

    render(createElement(AdminLayout, { children: null }))
    expect(container.textContent).toContain('discovering…')
  })
})
