import { beforeEach, describe, expect, it, vi } from 'vitest'

const cmsApiMock = vi.fn()
vi.mock('../../lib/api.js', () => ({
  cmsApi: (...args: unknown[]) => cmsApiMock(...args),
}))

const {
  fetchPostTypeDetail,
  fieldKeyFromName,
  fieldTypeLabel,
  humanizeFieldName,
  renderFieldSnippet,
} = await import('../../lib/post-types-service.js')

const COLLECTIONS = [
  {
    slug: 'blog',
    labels: { singular: 'Blog Post', plural: 'Blog' },
    type: 'post',
    urlPrefix: 'blog',
    description: 'Long-form articles',
    icon: 'file-text',
    color: 'purple',
    hidden: false,
    fields: [
      { name: 'title', type: 'text', required: true },
      { name: 'body', type: 'richText', required: false },
      { name: 'author', type: 'relationship', required: false, relationTo: 'users' },
    ],
  },
]

describe('fetchPostTypeDetail', () => {
  beforeEach(() => {
    cmsApiMock.mockReset()
    cmsApiMock.mockResolvedValue({ data: COLLECTIONS })
  })

  it('maps the matching collection row onto PostTypeDetail', async () => {
    const detail = await fetchPostTypeDetail('blog')
    expect(detail).toEqual({
      slug: 'blog',
      singular: 'Blog Post',
      plural: 'Blog',
      description: 'Long-form articles',
      urlPrefix: 'blog',
      icon: 'file-text',
      color: 'purple',
      fields: COLLECTIONS[0]!.fields,
    })
  })

  it('returns null when the slug is unknown', async () => {
    expect(await fetchPostTypeDetail('missing')).toBeNull()
  })

  it('throws on an API error envelope', async () => {
    cmsApiMock.mockResolvedValue({ error: 'boom' })
    await expect(fetchPostTypeDetail('blog')).rejects.toThrow('boom')
  })
})

describe('field name helpers', () => {
  it('humanizes camelCase and snake_case keys', () => {
    expect(humanizeFieldName('clientName')).toBe('Client Name')
    expect(humanizeFieldName('meta_description')).toBe('Meta Description')
    expect(humanizeFieldName('seo-score')).toBe('Seo Score')
  })

  it('derives camelCase keys from display names', () => {
    expect(fieldKeyFromName('Client Name')).toBe('clientName')
    expect(fieldKeyFromName('  Featured   Image!  ')).toBe('featuredImage')
    expect(fieldKeyFromName('')).toBe('')
  })

  it('labels known field types and passes through unknown ones', () => {
    expect(fieldTypeLabel('text')).toBe('Short Text')
    expect(fieldTypeLabel('relationship')).toBe('Relation')
    expect(fieldTypeLabel('customThing')).toBe('customThing')
  })
})

describe('renderFieldSnippet', () => {
  it('renders a minimal optional field', () => {
    expect(
      renderFieldSnippet({ key: 'subtitle', label: 'Subtitle', type: 'text', required: false }),
    ).toBe("subtitle: { type: 'text', label: 'Subtitle' },")
  })

  it('includes required, help text, and escapes quotes', () => {
    const snippet = renderFieldSnippet({
      key: 'clientName',
      label: "Client's Name",
      type: 'text',
      required: true,
      helpText: "Editor's hint",
    })
    expect(snippet).toContain("label: 'Client\\'s Name'")
    expect(snippet).toContain('required: true')
    expect(snippet).toContain("admin: { description: 'Editor\\'s hint' }")
  })

  it('seeds placeholder options for select and relationTo for relationship', () => {
    expect(
      renderFieldSnippet({ key: 'tone', label: 'Tone', type: 'select', required: false }),
    ).toContain("options: [{ label: 'Option A', value: 'option-a' }]")
    expect(
      renderFieldSnippet({ key: 'owner', label: 'Owner', type: 'relationship', required: false }),
    ).toContain("relationTo: 'posts'")
  })
})
