import { describe, expect, it, vi } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { FIELD_TYPES } from '@platform-modules/fields'
import { FieldsProvider } from './provider.js'
import type { FieldsClient, ResolvedFieldGroup } from './client.js'
import { FieldGroupEditor } from './group-editor.js'

const existingGroup: ResolvedFieldGroup = {
  id: 'g1',
  key: 'attrs',
  label: 'Attributes',
  location: { entityType: 'content' },
  origin: 'db',
  fields: [
    { key: 'subtitle', type: 'text', label: 'Subtitle' },
    { key: 'summary', type: 'textarea', label: 'Summary' },
  ],
}

function makeClient(overrides: Partial<FieldsClient> = {}) {
  return {
    getEntityValues: vi.fn(async () => ({})),
    setEntityValues: vi.fn(async () => {}),
    resolveGroups: vi.fn(async () => [existingGroup]),
    listGroups: vi.fn(async () => [existingGroup]),
    createGroup: vi.fn(async (g) => ({ ...g, origin: 'db' as const, id: 'new-id' })),
    updateGroup: vi.fn(async (_id, patch) => ({ ...existingGroup, ...patch })),
    deleteGroup: vi.fn(async () => {}),
    ...overrides,
  } as unknown as FieldsClient
}

function renderEditor(client = makeClient()) {
  render(
    <FieldsProvider client={client}>
      <FieldGroupEditor entityType="content" />
    </FieldsProvider>,
  )
  return client
}

describe('FieldGroupEditor', () => {
  it('lists existing field definitions with labelled controls', async () => {
    renderEditor()
    await waitFor(() => expect(screen.getByLabelText('Subtitle')).toBeTruthy())
    expect(screen.getByLabelText('Summary')).toBeTruthy()
    expect(screen.getByLabelText('Field key', { selector: '#field-key-subtitle' })).toBeTruthy()
  })

  it('appends a row with a type select populated from FIELD_TYPES when Add field is clicked', async () => {
    renderEditor()
    await waitFor(() => expect(screen.getByLabelText('Subtitle')).toBeTruthy())
    fireEvent.click(screen.getByRole('button', { name: 'Add field' }))
    const typeSelect = screen.getByLabelText('Field type', {
      selector: 'select[name="field-type-new"]',
    }) as HTMLSelectElement
    const optionValues = Array.from(typeSelect.options).map((o) => o.value)
    expect(optionValues).toEqual([...FIELD_TYPES])
  })

  it('shows a focusable role=alert for an invalid key and blocks save', async () => {
    renderEditor()
    await waitFor(() => expect(screen.getByLabelText('Subtitle')).toBeTruthy())
    const keyInput = screen.getByLabelText('Field key', {
      selector: '#field-key-subtitle',
    }) as HTMLInputElement
    fireEvent.change(keyInput, { target: { value: '2bad' } })
    const alert = screen.getByRole('alert')
    expect(alert.textContent).toMatch(/invalid/i)
    expect(alert.getAttribute('tabindex')).toBe('0')
    expect((screen.getByRole('button', { name: 'Save group' }) as HTMLButtonElement).disabled).toBe(
      true,
    )
  })

  it('keeps keyboard focus on move-up at the first-field boundary via aria-disabled', async () => {
    renderEditor()
    await waitFor(() => expect(screen.getByLabelText('Subtitle')).toBeTruthy())
    const moveUp = screen.getByRole('button', { name: 'Move Subtitle up' }) as HTMLButtonElement
    moveUp.focus()
    fireEvent.click(moveUp)
    expect(moveUp.getAttribute('aria-disabled')).toBe('true')
    expect(document.activeElement).toBe(moveUp)
    expect(document.body.contains(moveUp)).toBe(true)
    const labels = screen.getAllByLabelText(/^(Subtitle|Summary)$/)
    expect((labels[0] as HTMLInputElement).getAttribute('aria-label')).toBe('Subtitle')
    expect((labels[1] as HTMLInputElement).getAttribute('aria-label')).toBe('Summary')
  })

  it('reorders field definitions via move-up and move-down buttons', async () => {
    renderEditor()
    await waitFor(() => expect(screen.getByLabelText('Subtitle')).toBeTruthy())
    const moveDownSubtitle = screen.getByRole('button', { name: 'Move Subtitle down' })
    fireEvent.click(moveDownSubtitle)
    const labels = screen.getAllByLabelText(/^(Subtitle|Summary)$/)
    expect((labels[0] as HTMLInputElement).getAttribute('aria-label')).toBe('Summary')
    expect((labels[1] as HTMLInputElement).getAttribute('aria-label')).toBe('Subtitle')
    const moveUpSubtitle = screen.getByRole('button', { name: 'Move Subtitle up' })
    fireEvent.click(moveUpSubtitle)
    const labelsAgain = screen.getAllByLabelText(/^(Subtitle|Summary)$/)
    expect((labelsAgain[0] as HTMLInputElement).getAttribute('aria-label')).toBe('Subtitle')
    expect((labelsAgain[1] as HTMLInputElement).getAttribute('aria-label')).toBe('Summary')
  })

  it('calls updateGroup with the assembled field group on save', async () => {
    const client = makeClient()
    renderEditor(client)
    await waitFor(() => expect(screen.getByLabelText('Subtitle')).toBeTruthy())
    fireEvent.change(screen.getByLabelText('Subtitle'), { target: { value: 'Tagline' } })
    fireEvent.click(screen.getByRole('button', { name: 'Save group' }))
    await waitFor(() => expect(client.updateGroup).toHaveBeenCalled())
    const [, patch] = (client.updateGroup as ReturnType<typeof vi.fn>).mock.calls[0]!
    expect(patch.fields).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ key: 'subtitle', label: 'Tagline' }),
        expect.objectContaining({ key: 'summary', type: 'textarea' }),
      ]),
    )
  })
})
