// @vitest-environment jsdom
//
// <ContactForm> — the island inside the static contact page. It POSTs
// { name, email, message } as JSON to CONTACT_ENDPOINT and reflects the
// request lifecycle (idle → sending → sent | error). We mock `island` to a
// passthrough (so the bare component renders without the marker wrapper),
// pin CONTACT_ENDPOINT, and stub `fetch` to assert the submit payload and the
// success + error UI transitions.
//
// The island is i18n-agnostic: it takes all its UI copy as a `labels` prop
// (the page resolves it per-locale). We pass the English labels here so the
// assertions read the same strings the page would supply.

import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { act, createElement, type ComponentType, type ReactNode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import type { ContactFormLabels } from './ContactForm.island'

vi.mock('@voltro/web', () => ({
  // Passthrough: return the component untouched so we test its own behavior,
  // not the island marker wrapper.
  island: (Component: ComponentType<unknown>) => Component,
}))

vi.mock('../config', () => ({ CONTACT_ENDPOINT: 'https://fn.test/contact' }))

const { default: ContactForm } = await import('./ContactForm.island')

// English labels — the same strings the page resolves from the en catalog.
const labels: ContactFormLabels = {
  name: 'Name',
  email: 'Email',
  message: 'Message',
  send: 'Send message',
  sending: 'Sending…',
  sentNotice: 'Thanks — your message is on its way. ✓',
  errorPrefix: 'Could not send:',
  networkError: 'network error',
}

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

let container: HTMLDivElement
let root: Root
const fetchMock = vi.fn<typeof fetch>()

const render = (node: ReactNode): void => {
  container = document.createElement('div')
  document.body.appendChild(container)
  act(() => {
    root = createRoot(container)
    root.render(node)
  })
}

const setInput = (el: HTMLInputElement | HTMLTextAreaElement, value: string): void => {
  const proto =
    el instanceof window.HTMLTextAreaElement
      ? window.HTMLTextAreaElement.prototype
      : window.HTMLInputElement.prototype
  const setter = Object.getOwnPropertyDescriptor(proto, 'value')!.set!
  act(() => {
    setter.call(el, value)
    el.dispatchEvent(new Event('input', { bubbles: true }))
  })
}

const fill = (): void => {
  setInput(container.querySelector('#name') as HTMLInputElement, 'Ada')
  setInput(container.querySelector('#email') as HTMLInputElement, 'ada@example.com')
  setInput(container.querySelector('#message') as HTMLTextAreaElement, 'hello there')
}

const submit = async (): Promise<void> => {
  const form = container.querySelector('form') as HTMLFormElement
  await act(async () => {
    form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
  })
}

beforeEach(() => {
  fetchMock.mockReset()
  vi.stubGlobal('fetch', fetchMock)
})

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

describe('ContactForm — render', () => {
  test('renders the name/email/message fields and a submit button', () => {
    render(createElement(ContactForm, { labels }))
    expect(container.querySelector('#name')).toBeTruthy()
    expect(container.querySelector('#email')).toBeTruthy()
    expect(container.querySelector('#message')).toBeTruthy()
    expect((container.querySelector('button[type="submit"]') as HTMLButtonElement).textContent)
      .toContain('Send message')
  })
})

describe('ContactForm — submit', () => {
  test('POSTs the form values as JSON to CONTACT_ENDPOINT and shows the sent notice', async () => {
    fetchMock.mockResolvedValue(new Response(JSON.stringify({ ok: true }), { status: 200 }))
    render(createElement(ContactForm, { labels }))
    fill()
    await submit()

    expect(fetchMock).toHaveBeenCalledTimes(1)
    const [url, init] = fetchMock.mock.calls[0]!
    expect(url).toBe('https://fn.test/contact')
    expect(init?.method).toBe('POST')
    expect(JSON.parse(init?.body as string)).toEqual({
      name: 'Ada',
      email: 'ada@example.com',
      message: 'hello there',
    })
    expect(container.textContent).toContain('on its way')
  })

  test('surfaces the server error detail when the response is not ok', async () => {
    fetchMock.mockResolvedValue(
      new Response(JSON.stringify({ detail: 'rate limited' }), { status: 429 }),
    )
    render(createElement(ContactForm, { labels }))
    fill()
    await submit()
    expect(container.textContent).toContain('Could not send')
    expect(container.textContent).toContain('rate limited')
  })

  test('surfaces a network error when fetch rejects', async () => {
    fetchMock.mockRejectedValue(new Error('offline'))
    render(createElement(ContactForm, { labels }))
    fill()
    await submit()
    expect(container.textContent).toContain('Could not send')
    expect(container.textContent).toContain('offline')
  })
})
