import { render, screen, fireEvent } from '@testing-library/react'
import { TextBlockEditor } from '../text-block'
import { MetricsBlockEditor } from '../metrics-block'
import { DividerBlockEditor } from '../divider-block'
import { ButtonBlockEditor } from '../button-block'
import { ImageBlockEditor } from '../image-block'
import { SpacerBlockEditor } from '../spacer-block'
import { SocialBlockEditor } from '../social-block'
import { HeaderBlockEditor } from '../header-block'
import { FooterBlockEditor } from '../footer-block'
import type {
  TextBlock,
  MetricsBlock,
  DividerBlock,
  CTABlock,
  ImageBlock,
  SpacerBlock,
  SocialBlock,
  HeaderBlock,
  FooterBlock,
} from '../../types'

// ---------------------------------------------------------------------------
// Shared test fixtures
// ---------------------------------------------------------------------------

const baseTextBlock: TextBlock = {
  id: 'text-1',
  type: 'text',
  content: '<p>Hello world</p>',
}

const baseMetricsBlock: MetricsBlock = {
  id: 'metrics-1',
  type: 'metrics',
  title: 'Key Metrics',
  metrics: [
    { id: 'metric-a', label: 'Revenue', value: '$1,200', change: '+10%', changeType: 'positive' },
    { id: 'metric-b', label: 'Users', value: '340', change: '-5%', changeType: 'negative' },
  ],
  columns: 2,
}

const baseDividerBlock: DividerBlock = {
  id: 'divider-1',
  type: 'divider',
  dividerStyle: 'solid',
  color: '#d1d5db',
  thickness: 1,
  width: 100,
}

const baseButtonBlock: CTABlock = {
  id: 'cta-1',
  type: 'cta',
  text: 'Click Me',
  url: 'https://example.com',
  buttonColor: '#2563eb',
  textColor: '#ffffff',
  borderRadius: 6,
  paddingH: 24,
  paddingV: 12,
  alignment: 'center',
}

const baseImageBlock: ImageBlock = {
  id: 'image-1',
  type: 'image',
  url: 'https://example.com/image.png',
  alt: 'Test image',
  alignment: 'center',
  width: 100,
}

const baseSpacerBlock: SpacerBlock = {
  id: 'spacer-1',
  type: 'spacer',
  height: 48,
}

const baseSocialBlock: SocialBlock = {
  id: 'social-1',
  type: 'social',
  links: [
    { id: 'link-linkedin', platform: 'linkedin', url: 'https://linkedin.com/in/test' },
    { id: 'link-twitter', platform: 'twitter', url: 'https://twitter.com/test' },
    { id: 'link-website', platform: 'website', url: '' },
  ],
  iconSize: 24,
  alignment: 'center',
}

const baseHeaderBlock: HeaderBlock = {
  id: 'header-1',
  type: 'header',
  companyName: 'Acme Corp',
  alignment: 'center',
}

const baseFooterBlock: FooterBlock = {
  id: 'footer-1',
  type: 'footer',
  companyName: 'Acme Corp',
  address: '123 Main St, Springfield',
  showUnsubscribe: true,
  unsubscribeUrl: 'https://example.com/unsubscribe',
  alignment: 'center',
}

// ---------------------------------------------------------------------------
// TextBlockEditor
// ---------------------------------------------------------------------------

describe('TextBlockEditor', () => {
  it('renders content in view mode via dangerouslySetInnerHTML', () => {
    const { container } = render(
      <TextBlockEditor block={baseTextBlock} onChange={jest.fn()} isEditing={false} />
    )
    expect(container.querySelector('.prose')).toBeInTheDocument()
  })

  it('renders contentEditable div in editing mode without renderEditor', () => {
    const { container } = render(
      <TextBlockEditor block={baseTextBlock} onChange={jest.fn()} isEditing={true} />
    )
    expect(container.querySelector('[contenteditable="true"]')).toBeInTheDocument()
  })

  it('applies fontSize style from block data', () => {
    const block: TextBlock = { ...baseTextBlock, fontSize: 18 }
    const { container } = render(
      <TextBlockEditor block={block} onChange={jest.fn()} isEditing={false} />
    )
    const el = container.querySelector('.prose') as HTMLElement
    expect(el.style.fontSize).toBe('18px')
  })

  it('applies fontFamily style from block data', () => {
    const block: TextBlock = { ...baseTextBlock, fontFamily: 'Georgia, serif' }
    const { container } = render(
      <TextBlockEditor block={block} onChange={jest.fn()} isEditing={false} />
    )
    const el = container.querySelector('.prose') as HTMLElement
    expect(el.style.fontFamily).toBe('Georgia, serif')
  })

  it('applies textColor style from block data', () => {
    const block: TextBlock = { ...baseTextBlock, textColor: '#ff0000' }
    const { container } = render(
      <TextBlockEditor block={block} onChange={jest.fn()} isEditing={false} />
    )
    const el = container.querySelector('.prose') as HTMLElement
    expect(el.style.color).toBe('rgb(255, 0, 0)')
  })

  it('calls onChange with updated content on blur in default editing mode', () => {
    const onChange = jest.fn()
    const { container } = render(
      <TextBlockEditor block={baseTextBlock} onChange={onChange} isEditing={true} />
    )
    const editable = container.querySelector('[contenteditable="true"]') as HTMLElement
    fireEvent.blur(editable)
    expect(onChange).toHaveBeenCalledTimes(1)
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'text', id: 'text-1' })
    )
  })

  it('uses renderEditor when provided in editing mode', () => {
    const renderEditor = jest.fn(() => <div data-testid="custom-editor" />)
    render(
      <TextBlockEditor
        block={baseTextBlock}
        onChange={jest.fn()}
        isEditing={true}
        renderEditor={renderEditor}
      />
    )
    expect(screen.getByTestId('custom-editor')).toBeInTheDocument()
    expect(renderEditor).toHaveBeenCalledWith(
      expect.objectContaining({
        content: baseTextBlock.content,
        placeholder: 'Write your content...',
      })
    )
  })

  it('renderEditor onChange callback updates block content', () => {
    const onChange = jest.fn()
    let capturedOnChange: (html: string) => void = () => {}
    const renderEditor = jest.fn(({ onChange: cb }: { onChange: (html: string) => void }) => {
      capturedOnChange = cb
      return <div data-testid="custom-editor" />
    })
    render(
      <TextBlockEditor
        block={baseTextBlock}
        onChange={onChange}
        isEditing={true}
        renderEditor={renderEditor}
      />
    )
    capturedOnChange('<p>Updated</p>')
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ content: '<p>Updated</p>' })
    )
  })

  it('falls back to empty string for content in editing mode when content is empty', () => {
    const block: TextBlock = { ...baseTextBlock, content: '' }
    const { container } = render(
      <TextBlockEditor block={block} onChange={jest.fn()} isEditing={true} />
    )
    expect(container.querySelector('[contenteditable="true"]')).toBeInTheDocument()
  })

  it('defaults to isEditing=true when prop is omitted', () => {
    const { container } = render(
      <TextBlockEditor block={baseTextBlock} onChange={jest.fn()} />
    )
    expect(container.querySelector('[contenteditable="true"]')).toBeInTheDocument()
  })
})

// ---------------------------------------------------------------------------
// MetricsBlockEditor
// ---------------------------------------------------------------------------

describe('MetricsBlockEditor', () => {
  it('renders metric values and labels in view mode', () => {
    render(<MetricsBlockEditor block={baseMetricsBlock} onChange={jest.fn()} isEditing={false} />)
    expect(screen.getByText('$1,200')).toBeInTheDocument()
    expect(screen.getByText('Revenue')).toBeInTheDocument()
    expect(screen.getByText('340')).toBeInTheDocument()
    expect(screen.getByText('Users')).toBeInTheDocument()
  })

  it('renders section title in view mode when provided', () => {
    render(<MetricsBlockEditor block={baseMetricsBlock} onChange={jest.fn()} isEditing={false} />)
    expect(screen.getByText('Key Metrics')).toBeInTheDocument()
  })

  it('does not render title in view mode when omitted', () => {
    const block: MetricsBlock = { ...baseMetricsBlock, title: undefined }
    render(<MetricsBlockEditor block={block} onChange={jest.fn()} isEditing={false} />)
    expect(screen.queryByRole('heading')).not.toBeInTheDocument()
  })

  it('renders change indicator for positive metric in view mode', () => {
    render(<MetricsBlockEditor block={baseMetricsBlock} onChange={jest.fn()} isEditing={false} />)
    expect(screen.getByText('+10%')).toBeInTheDocument()
  })

  it('renders change indicator for negative metric in view mode', () => {
    render(<MetricsBlockEditor block={baseMetricsBlock} onChange={jest.fn()} isEditing={false} />)
    expect(screen.getByText('-5%')).toBeInTheDocument()
  })

  it('does not render change when metric has no change field', () => {
    const block: MetricsBlock = {
      ...baseMetricsBlock,
      metrics: [{ id: 'metric-c', label: 'Sessions', value: '100' }],
    }
    render(<MetricsBlockEditor block={block} onChange={jest.fn()} isEditing={false} />)
    expect(screen.getByText('Sessions')).toBeInTheDocument()
    expect(screen.getByText('100')).toBeInTheDocument()
  })

  it('renders section title input and columns select in editing mode', () => {
    render(<MetricsBlockEditor block={baseMetricsBlock} onChange={jest.fn()} isEditing={true} />)
    expect(screen.getByPlaceholderText('Key Metrics')).toBeInTheDocument()
    expect(screen.getByText('Add Metric')).toBeInTheDocument()
  })

  it('calls onChange when title input changes in editing mode', () => {
    const onChange = jest.fn()
    render(<MetricsBlockEditor block={baseMetricsBlock} onChange={onChange} isEditing={true} />)
    const titleInput = screen.getByPlaceholderText('Key Metrics')
    fireEvent.change(titleInput, { target: { value: 'Updated Title' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ title: 'Updated Title' })
    )
  })

  it('calls onChange with new metric when Add Metric is clicked', () => {
    const onChange = jest.fn()
    render(<MetricsBlockEditor block={baseMetricsBlock} onChange={onChange} isEditing={true} />)
    fireEvent.click(screen.getByText('Add Metric'))
    expect(onChange).toHaveBeenCalledTimes(1)
    const updated: MetricsBlock = onChange.mock.calls[0][0]
    expect(updated.metrics).toHaveLength(3)
    expect(updated.metrics[2].label).toBe('New Metric')
  })

  it('calls onChange with metric removed when remove button is clicked', () => {
    const onChange = jest.fn()
    render(<MetricsBlockEditor block={baseMetricsBlock} onChange={onChange} isEditing={true} />)
    // Two metrics — two remove buttons; first button should be enabled
    const removeButtons = screen.getAllByRole('button').filter((btn) =>
      btn.querySelector('svg') !== null && !btn.textContent?.includes('Add')
    )
    fireEvent.click(removeButtons[0])
    expect(onChange).toHaveBeenCalledTimes(1)
    const updated: MetricsBlock = onChange.mock.calls[0][0]
    expect(updated.metrics).toHaveLength(1)
  })

  it('disables remove button when only one metric remains', () => {
    const block: MetricsBlock = {
      ...baseMetricsBlock,
      metrics: [{ id: 'only', label: 'Solo', value: '1' }],
    }
    render(<MetricsBlockEditor block={block} onChange={jest.fn()} isEditing={true} />)
    const removeButtons = screen.getAllByRole('button', { hidden: true }).filter(
      (btn) => btn.hasAttribute('disabled')
    )
    expect(removeButtons.length).toBeGreaterThan(0)
  })

  it('calls onChange when a metric label input changes', () => {
    const onChange = jest.fn()
    render(<MetricsBlockEditor block={baseMetricsBlock} onChange={onChange} isEditing={true} />)
    const labelInputs = screen.getAllByPlaceholderText('Label')
    fireEvent.change(labelInputs[0], { target: { value: 'ARR' } })
    expect(onChange).toHaveBeenCalledTimes(1)
    const updated: MetricsBlock = onChange.mock.calls[0][0]
    expect(updated.metrics[0].label).toBe('ARR')
  })

  it('calls onChange when a metric value input changes', () => {
    const onChange = jest.fn()
    render(<MetricsBlockEditor block={baseMetricsBlock} onChange={onChange} isEditing={true} />)
    const valueInputs = screen.getAllByPlaceholderText('Value')
    fireEvent.change(valueInputs[0], { target: { value: '$9,999' } })
    expect(onChange).toHaveBeenCalledTimes(1)
    const updated: MetricsBlock = onChange.mock.calls[0][0]
    expect(updated.metrics[0].value).toBe('$9,999')
  })

  it('defaults to isEditing=true when prop is omitted', () => {
    render(<MetricsBlockEditor block={baseMetricsBlock} onChange={jest.fn()} />)
    expect(screen.getByText('Add Metric')).toBeInTheDocument()
  })
})

// ---------------------------------------------------------------------------
// DividerBlockEditor
// ---------------------------------------------------------------------------

describe('DividerBlockEditor', () => {
  it('renders an hr element in view mode for solid style', () => {
    const { container } = render(
      <DividerBlockEditor block={baseDividerBlock} onChange={jest.fn()} isEditing={false} />
    )
    expect(container.querySelector('hr')).toBeInTheDocument()
  })

  it('applies correct border-top style to hr in view mode', () => {
    const { container } = render(
      <DividerBlockEditor block={baseDividerBlock} onChange={jest.fn()} isEditing={false} />
    )
    const hr = container.querySelector('hr') as HTMLElement
    expect(hr.style.borderTop).toContain('solid')
  })

  it('renders a spacer div (no hr) when dividerStyle is "space"', () => {
    const block: DividerBlock = { ...baseDividerBlock, dividerStyle: 'space' }
    const { container } = render(
      <DividerBlockEditor block={block} onChange={jest.fn()} isEditing={false} />
    )
    expect(container.querySelector('hr')).not.toBeInTheDocument()
    const spacer = container.querySelector('div') as HTMLElement
    expect(spacer.style.height).toBe('32px')
  })

  it('renders with dashed style', () => {
    const block: DividerBlock = { ...baseDividerBlock, dividerStyle: 'dashed' }
    const { container } = render(
      <DividerBlockEditor block={block} onChange={jest.fn()} isEditing={false} />
    )
    const hr = container.querySelector('hr') as HTMLElement
    expect(hr.style.borderTop).toContain('dashed')
  })

  it('renders with dotted style', () => {
    const block: DividerBlock = { ...baseDividerBlock, dividerStyle: 'dotted' }
    const { container } = render(
      <DividerBlockEditor block={block} onChange={jest.fn()} isEditing={false} />
    )
    const hr = container.querySelector('hr') as HTMLElement
    expect(hr.style.borderTop).toContain('dotted')
  })

  it('applies width percentage to hr', () => {
    const block: DividerBlock = { ...baseDividerBlock, width: 80 }
    const { container } = render(
      <DividerBlockEditor block={block} onChange={jest.fn()} isEditing={false} />
    )
    const hr = container.querySelector('hr') as HTMLElement
    expect(hr.style.width).toBe('80%')
  })

  it('renders a preview hr in editing mode', () => {
    const { container } = render(
      <DividerBlockEditor block={baseDividerBlock} onChange={jest.fn()} isEditing={true} />
    )
    // Editing mode wraps the view-mode render, so hr should still appear
    expect(container.querySelector('hr')).toBeInTheDocument()
  })

  it('uses default color when color is not provided', () => {
    const block: DividerBlock = { ...baseDividerBlock, color: undefined }
    const { container } = render(
      <DividerBlockEditor block={block} onChange={jest.fn()} isEditing={false} />
    )
    const hr = container.querySelector('hr') as HTMLElement
    // jsdom normalizes hex colors to rgb in computed styles
    expect(hr.style.borderTop).toMatch(/rgb\(209,\s*213,\s*219\)|#d1d5db/)
  })
})

// ---------------------------------------------------------------------------
// ButtonBlockEditor
// ---------------------------------------------------------------------------

describe('ButtonBlockEditor', () => {
  it('renders the button text from block data', () => {
    render(<ButtonBlockEditor block={baseButtonBlock} onChange={jest.fn()} />)
    expect(screen.getByText('Click Me')).toBeInTheDocument()
  })

  it('falls back to "Button" text when block.text is empty', () => {
    const block: CTABlock = { ...baseButtonBlock, text: '' }
    render(<ButtonBlockEditor block={block} onChange={jest.fn()} />)
    expect(screen.getByText('Button')).toBeInTheDocument()
  })

  it('applies button background color from block data', () => {
    const { container } = render(<ButtonBlockEditor block={baseButtonBlock} onChange={jest.fn()} />)
    const span = container.querySelector('span[style]') as HTMLElement
    expect(span.style.backgroundColor).toBe('rgb(37, 99, 235)')
  })

  it('applies text color from block data', () => {
    const { container } = render(<ButtonBlockEditor block={baseButtonBlock} onChange={jest.fn()} />)
    const span = container.querySelector('span[style]') as HTMLElement
    expect(span.style.color).toBe('rgb(255, 255, 255)')
  })

  it('applies border radius from block data', () => {
    const { container } = render(<ButtonBlockEditor block={baseButtonBlock} onChange={jest.fn()} />)
    const span = container.querySelector('span[style]') as HTMLElement
    expect(span.style.borderRadius).toBe('6px')
  })

  it('uses center alignment class by default', () => {
    const { container } = render(<ButtonBlockEditor block={baseButtonBlock} onChange={jest.fn()} />)
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('text-center')
  })

  it('applies left alignment class', () => {
    const block: CTABlock = { ...baseButtonBlock, alignment: 'left' }
    const { container } = render(<ButtonBlockEditor block={block} onChange={jest.fn()} />)
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('text-left')
  })

  it('applies right alignment class', () => {
    const block: CTABlock = { ...baseButtonBlock, alignment: 'right' }
    const { container } = render(<ButtonBlockEditor block={block} onChange={jest.fn()} />)
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('text-right')
  })

  it('uses default buttonColor when not provided', () => {
    const block: CTABlock = { ...baseButtonBlock, buttonColor: undefined }
    const { container } = render(<ButtonBlockEditor block={block} onChange={jest.fn()} />)
    const span = container.querySelector('span[style]') as HTMLElement
    expect(span.style.backgroundColor).toBe('rgb(37, 99, 235)')
  })
})

// ---------------------------------------------------------------------------
// ImageBlockEditor
// ---------------------------------------------------------------------------

describe('ImageBlockEditor', () => {
  it('renders img element when url is provided', () => {
    const { container } = render(
      <ImageBlockEditor block={baseImageBlock} onChange={jest.fn()} />
    )
    const img = container.querySelector('img') as HTMLImageElement
    expect(img).toBeInTheDocument()
    expect(img.src).toBe('https://example.com/image.png')
    expect(img.alt).toBe('Test image')
  })

  it('renders placeholder when url is empty', () => {
    const block: ImageBlock = { ...baseImageBlock, url: '' }
    render(<ImageBlockEditor block={block} onChange={jest.fn()} isEditing={true} />)
    expect(screen.getByText('Click to set image URL')).toBeInTheDocument()
  })

  it('renders "No image set" placeholder text in view mode when url is empty', () => {
    const block: ImageBlock = { ...baseImageBlock, url: '' }
    render(<ImageBlockEditor block={block} onChange={jest.fn()} isEditing={false} />)
    expect(screen.getByText('No image set')).toBeInTheDocument()
  })

  it('renders caption when provided', () => {
    const block: ImageBlock = { ...baseImageBlock, caption: 'A beautiful photo' }
    render(<ImageBlockEditor block={block} onChange={jest.fn()} />)
    expect(screen.getByText('A beautiful photo')).toBeInTheDocument()
  })

  it('does not render caption element when caption is absent', () => {
    render(<ImageBlockEditor block={baseImageBlock} onChange={jest.fn()} />)
    expect(screen.queryByRole('paragraph')).not.toBeInTheDocument()
  })

  it('wraps image in an anchor when linkUrl is set in view mode', () => {
    const block: ImageBlock = { ...baseImageBlock, linkUrl: 'https://linked.com' }
    const { container } = render(
      <ImageBlockEditor block={block} onChange={jest.fn()} isEditing={false} />
    )
    const anchor = container.querySelector('a') as HTMLAnchorElement
    expect(anchor).toBeInTheDocument()
    expect(anchor.href).toBe('https://linked.com/')
  })

  it('does not wrap image in anchor in editing mode even when linkUrl is set', () => {
    const block: ImageBlock = { ...baseImageBlock, linkUrl: 'https://linked.com' }
    const { container } = render(
      <ImageBlockEditor block={block} onChange={jest.fn()} isEditing={true} />
    )
    expect(container.querySelector('a')).not.toBeInTheDocument()
  })

  it('applies center alignment class by default', () => {
    const { container } = render(
      <ImageBlockEditor block={baseImageBlock} onChange={jest.fn()} />
    )
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('text-center')
  })

  it('applies left alignment class', () => {
    const block: ImageBlock = { ...baseImageBlock, alignment: 'left' }
    const { container } = render(
      <ImageBlockEditor block={block} onChange={jest.fn()} />
    )
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('text-left')
  })

  it('applies width percentage to img element', () => {
    const block: ImageBlock = { ...baseImageBlock, width: 50 }
    const { container } = render(
      <ImageBlockEditor block={block} onChange={jest.fn()} />
    )
    const img = container.querySelector('img') as HTMLElement
    expect(img.style.width).toBe('50%')
  })
})

// ---------------------------------------------------------------------------
// SpacerBlockEditor
// ---------------------------------------------------------------------------

describe('SpacerBlockEditor', () => {
  it('renders a plain div with correct height in view mode', () => {
    const { container } = render(
      <SpacerBlockEditor block={baseSpacerBlock} onChange={jest.fn()} isEditing={false} />
    )
    const spacer = container.querySelector('div') as HTMLElement
    expect(spacer.style.height).toBe('48px')
  })

  it('renders editing mode wrapper with height label', () => {
    render(
      <SpacerBlockEditor block={baseSpacerBlock} onChange={jest.fn()} isEditing={true} />
    )
    expect(screen.getByText('48px')).toBeInTheDocument()
  })

  it('defaults to 32px height when height is 0', () => {
    const block: SpacerBlock = { ...baseSpacerBlock, height: 0 }
    const { container } = render(
      <SpacerBlockEditor block={block} onChange={jest.fn()} isEditing={false} />
    )
    const spacer = container.querySelector('div') as HTMLElement
    // height of 0 is falsy so falls back to 32
    expect(spacer.style.height).toBe('32px')
  })

  it('renders dashed border container in editing mode', () => {
    const { container } = render(
      <SpacerBlockEditor block={baseSpacerBlock} onChange={jest.fn()} isEditing={true} />
    )
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('border-dashed')
  })

  it('defaults to isEditing=true when prop is omitted', () => {
    render(<SpacerBlockEditor block={baseSpacerBlock} onChange={jest.fn()} />)
    expect(screen.getByText('48px')).toBeInTheDocument()
  })
})

// ---------------------------------------------------------------------------
// SocialBlockEditor
// ---------------------------------------------------------------------------

describe('SocialBlockEditor', () => {
  it('renders icons for each link in the block', () => {
    const { container } = render(
      <SocialBlockEditor block={baseSocialBlock} onChange={jest.fn()} />
    )
    // 3 links → 3 icon wrapper elements (span or a)
    const iconWrappers = container.querySelectorAll('[class*="inline-flex"]')
    expect(iconWrappers.length).toBe(3)
  })

  it('renders spans (not anchors) for links in editing mode', () => {
    const { container } = render(
      <SocialBlockEditor block={baseSocialBlock} onChange={jest.fn()} isEditing={true} />
    )
    expect(container.querySelectorAll('a')).toHaveLength(0)
    expect(container.querySelectorAll('span.inline-flex').length).toBeGreaterThan(0)
  })

  it('renders spans for links with empty url even in view mode', () => {
    // link-website has url: '' so should render span not anchor
    const { container } = render(
      <SocialBlockEditor block={baseSocialBlock} onChange={jest.fn()} isEditing={false} />
    )
    // linkedin and twitter have URLs → 2 anchors; website has no url → 1 span
    const anchors = container.querySelectorAll('a')
    expect(anchors).toHaveLength(2)
  })

  it('renders anchors with correct hrefs for links with URLs in view mode', () => {
    const { container } = render(
      <SocialBlockEditor block={baseSocialBlock} onChange={jest.fn()} isEditing={false} />
    )
    const anchors = container.querySelectorAll('a') as NodeListOf<HTMLAnchorElement>
    const hrefs = Array.from(anchors).map((a) => a.href)
    expect(hrefs).toContain('https://linkedin.com/in/test')
    expect(hrefs).toContain('https://twitter.com/test')
  })

  it('applies justify-center class for center alignment', () => {
    const { container } = render(
      <SocialBlockEditor block={baseSocialBlock} onChange={jest.fn()} />
    )
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('justify-center')
  })

  it('applies justify-start class for left alignment', () => {
    const block: SocialBlock = { ...baseSocialBlock, alignment: 'left' }
    const { container } = render(<SocialBlockEditor block={block} onChange={jest.fn()} />)
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('justify-start')
  })

  it('applies justify-end class for right alignment', () => {
    const block: SocialBlock = { ...baseSocialBlock, alignment: 'right' }
    const { container } = render(<SocialBlockEditor block={block} onChange={jest.fn()} />)
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('justify-end')
  })

  it('renders empty links array without crashing', () => {
    const block: SocialBlock = { ...baseSocialBlock, links: [] }
    const { container } = render(<SocialBlockEditor block={block} onChange={jest.fn()} />)
    expect(container.querySelector('div')).toBeInTheDocument()
  })
})

// ---------------------------------------------------------------------------
// HeaderBlockEditor
// ---------------------------------------------------------------------------

describe('HeaderBlockEditor', () => {
  it('renders company name', () => {
    render(<HeaderBlockEditor block={baseHeaderBlock} onChange={jest.fn()} />)
    expect(screen.getByText('Acme Corp')).toBeInTheDocument()
  })

  it('renders logo image when logoUrl is provided', () => {
    const block: HeaderBlock = { ...baseHeaderBlock, logoUrl: 'https://example.com/logo.png' }
    const { container } = render(<HeaderBlockEditor block={block} onChange={jest.fn()} />)
    const img = container.querySelector('img') as HTMLImageElement
    expect(img).toBeInTheDocument()
    expect(img.src).toBe('https://example.com/logo.png')
    expect(img.alt).toBe('Acme Corp')
  })

  it('renders placeholder icon when logoUrl is absent', () => {
    const { container } = render(<HeaderBlockEditor block={baseHeaderBlock} onChange={jest.fn()} />)
    expect(container.querySelector('img')).not.toBeInTheDocument()
    // The placeholder wrapper div should be present
    expect(container.querySelector('.rounded.bg-gray-100')).toBeInTheDocument()
  })

  it('applies center alignment class', () => {
    const { container } = render(<HeaderBlockEditor block={baseHeaderBlock} onChange={jest.fn()} />)
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('text-center')
  })

  it('applies left alignment class', () => {
    const block: HeaderBlock = { ...baseHeaderBlock, alignment: 'left' }
    const { container } = render(<HeaderBlockEditor block={block} onChange={jest.fn()} />)
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('text-left')
  })

  it('applies right alignment class', () => {
    const block: HeaderBlock = { ...baseHeaderBlock, alignment: 'right' }
    const { container } = render(<HeaderBlockEditor block={block} onChange={jest.fn()} />)
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('text-right')
  })

  it('renders without logoUrl field (undefined)', () => {
    const block: HeaderBlock = { ...baseHeaderBlock, logoUrl: undefined }
    render(<HeaderBlockEditor block={block} onChange={jest.fn()} />)
    expect(screen.getByText('Acme Corp')).toBeInTheDocument()
  })
})

// ---------------------------------------------------------------------------
// FooterBlockEditor
// ---------------------------------------------------------------------------

describe('FooterBlockEditor', () => {
  it('renders company name', () => {
    render(<FooterBlockEditor block={baseFooterBlock} onChange={jest.fn()} />)
    expect(screen.getByText('Acme Corp')).toBeInTheDocument()
  })

  it('renders address when provided', () => {
    render(<FooterBlockEditor block={baseFooterBlock} onChange={jest.fn()} />)
    expect(screen.getByText('123 Main St, Springfield')).toBeInTheDocument()
  })

  it('does not render address element when address is absent', () => {
    const block: FooterBlock = { ...baseFooterBlock, address: undefined }
    render(<FooterBlockEditor block={block} onChange={jest.fn()} />)
    expect(screen.queryByText('123 Main St, Springfield')).not.toBeInTheDocument()
  })

  it('renders unsubscribe link when showUnsubscribe is true', () => {
    render(<FooterBlockEditor block={baseFooterBlock} onChange={jest.fn()} />)
    expect(screen.getByText('Unsubscribe')).toBeInTheDocument()
  })

  it('does not render unsubscribe link when showUnsubscribe is false', () => {
    const block: FooterBlock = { ...baseFooterBlock, showUnsubscribe: false }
    render(<FooterBlockEditor block={block} onChange={jest.fn()} />)
    expect(screen.queryByText('Unsubscribe')).not.toBeInTheDocument()
  })

  it('unsubscribe link href uses unsubscribeUrl when provided in view mode', () => {
    const { container } = render(
      <FooterBlockEditor block={baseFooterBlock} onChange={jest.fn()} isEditing={false} />
    )
    const anchor = container.querySelector('a.underline') as HTMLAnchorElement
    expect(anchor.href).toBe('https://example.com/unsubscribe')
  })

  it('unsubscribe link href falls back to "#" when unsubscribeUrl is absent', () => {
    const block: FooterBlock = { ...baseFooterBlock, unsubscribeUrl: undefined }
    const { container } = render(
      <FooterBlockEditor block={block} onChange={jest.fn()} isEditing={false} />
    )
    const anchor = container.querySelector('a.underline') as HTMLAnchorElement
    expect(anchor.getAttribute('href')).toBe('#')
  })

  it('prevents default click on unsubscribe link in editing mode', () => {
    render(<FooterBlockEditor block={baseFooterBlock} onChange={jest.fn()} isEditing={true} />)
    const anchor = screen.getByText('Unsubscribe')
    const event = createClickEvent()
    anchor.dispatchEvent(event)
    expect(event.defaultPrevented).toBe(true)
  })

  it('applies center alignment class', () => {
    const { container } = render(<FooterBlockEditor block={baseFooterBlock} onChange={jest.fn()} />)
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('text-center')
  })

  it('applies left alignment class', () => {
    const block: FooterBlock = { ...baseFooterBlock, alignment: 'left' }
    const { container } = render(<FooterBlockEditor block={block} onChange={jest.fn()} />)
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('text-left')
  })

  it('applies right alignment class', () => {
    const block: FooterBlock = { ...baseFooterBlock, alignment: 'right' }
    const { container } = render(<FooterBlockEditor block={block} onChange={jest.fn()} />)
    const wrapper = container.querySelector('div[class]') as HTMLElement
    expect(wrapper.className).toContain('text-right')
  })
})

// ---------------------------------------------------------------------------
// Helper: create a real MouseEvent with bubbles so dispatchEvent registers handler
// ---------------------------------------------------------------------------
function createClickEvent(): MouseEvent {
  return new MouseEvent('click', { bubbles: true, cancelable: true })
}
