import { describe, it, expect, vi } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { FormRenderer } from './FormRenderer.js'
import type { FormDef } from '@platform-modules/forms'
import type { SubmitFn } from './seams.js'

const form: FormDef = {
  id: 'contact',
  antispam: { honeypot: 'nickname' },
  fields: [
    { name: 'email', type: 'email', label: 'Email', required: true },
    { name: 'msg', type: 'textarea', label: 'Message' },
    { name: 'topic', type: 'select', label: 'Topic', options: [{ value: 'a', label: 'A' }, { value: 'b', label: 'B' }] },
    { name: 'tags', type: 'multiselect', label: 'Tags', options: [{ value: 'x', label: 'X' }, { value: 'y', label: 'Y' }] },
    { name: 'pick', type: 'radio', label: 'Pick', options: [{ value: 'p', label: 'P' }, { value: 'q', label: 'Q' }] },
    { name: 'agree', type: 'checkbox', label: 'Agree' },
  ],
}

describe('FormRenderer', () => {
  it('renders a labelled control for each field type', () => {
    render(<FormRenderer form={form} submit={vi.fn<SubmitFn>(async () => ({ ok: true, id: '1' }))} />)
    expect(screen.getByLabelText(/email/i)).toBeTruthy()
    expect(screen.getByLabelText(/message/i)).toBeTruthy()
    expect(screen.getByLabelText(/topic/i)).toBeTruthy()
    // group controls get a bound accessible NAME (not a dangling label)
    expect(screen.getByRole('group', { name: /tags/i })).toBeTruthy()
    expect(screen.getByRole('radiogroup', { name: /pick/i })).toBeTruthy()
  })

  it('renders the honeypot hidden + aria-hidden, and a hidden renderToken field', () => {
    const { container } = render(<FormRenderer form={form} renderToken="tok-123" submit={vi.fn<SubmitFn>(async () => ({ ok: true, id: '1' }))} />)
    const honeypot = container.querySelector('[name="nickname"]') as HTMLElement
    expect(honeypot).toBeTruthy()
    expect(honeypot.getAttribute('aria-hidden')).toBe('true')
    expect(honeypot.getAttribute('tabindex')).toBe('-1')
    const token = container.querySelector('input[type="hidden"][value="tok-123"]')
    expect(token).toBeTruthy()
  })

  it('calls onSuccess with the id on a successful submit', async () => {
    const onSuccess = vi.fn()
    const submit = vi.fn<SubmitFn>(async () => ({ ok: true, id: 's9' }))
    render(<FormRenderer form={form} submit={submit} onSuccess={onSuccess} />)
    fireEvent.click(screen.getByRole('button', { name: /submit/i }))
    await waitFor(() => expect(onSuccess).toHaveBeenCalledWith('s9'))
  })

  it('maps server field errors to the input via aria-describedby', async () => {
    const submit = vi.fn<SubmitFn>(async () => ({ ok: false, errors: [{ field: 'email', code: 'required', message: 'Email required' }] }))
    render(<FormRenderer form={form} submit={submit} />)
    fireEvent.click(screen.getByRole('button', { name: /submit/i }))
    await waitFor(() => expect(screen.getByText('Email required')).toBeTruthy())
    const email = screen.getByLabelText(/email/i)
    expect(email.getAttribute('aria-describedby')).toBeTruthy()
  })

  it('renders a focusable error-summary region on validation failure', async () => {
    const submit = vi.fn<SubmitFn>(async () => ({ ok: false, errors: [{ field: 'email', code: 'required', message: 'Email required' }] }))
    render(<FormRenderer form={form} submit={submit} />)
    fireEvent.click(screen.getByRole('button', { name: /submit/i }))
    // a focusable (tabIndex=-1) summary region is rendered among the alert regions
    await waitFor(() => {
      const focusable = screen.getAllByRole('alert').filter((el) => el.getAttribute('tabindex') === '-1')
      expect(focusable.length).toBe(1)
    })
  })
})
