import { describe, it, expect, vi } from 'vitest'
import { fireEvent, render, screen, within } from '@testing-library/react'
import { CategoryRowStacked } from './category-row-stacked'

const COLORS = ['rgb(0, 200, 0)', 'rgb(0, 100, 0)']

describe('<CategoryRowStacked>', () => {
  it('renders displayName ?? name on the left of the header', () => {
    render(
      <CategoryRowStacked
        name='cat-key'
        displayName='Cat'
        values={[45, 32]}
        colors={COLORS}
        seriesNames={['Women', 'Men']}
        formatter={(n) => String(n)}
        selected={false}
        onToggle={vi.fn()}
      />,
    )
    expect(screen.getByText('Cat')).toBeTruthy()
    expect(screen.queryByText('cat-key')).toBeNull()
  })

  it('falls back to the raw name when displayName is omitted', () => {
    render(
      <CategoryRowStacked
        name='Plain'
        values={[1]}
        colors={['rgb(0, 0, 0)']}
        seriesNames={['S']}
        formatter={(n) => String(n)}
        selected={false}
        onToggle={vi.fn()}
      />,
    )
    expect(screen.getByText('Plain')).toBeTruthy()
  })

  it('renders formatter(sum) on the right of the header', () => {
    render(
      <CategoryRowStacked
        name='Cat'
        values={[45, 32]}
        colors={COLORS}
        seriesNames={['Women', 'Men']}
        formatter={(n) => `${n}K`}
        selected={false}
        onToggle={vi.fn()}
      />,
    )
    expect(screen.getByText('77K')).toBeTruthy()
  })

  it('renders one breakdown entry per series, including zero values', () => {
    render(
      <CategoryRowStacked
        name='Cat'
        values={[45, 0, 32]}
        colors={[...COLORS, 'rgb(0, 50, 0)']}
        seriesNames={['Women', 'Other', 'Men']}
        formatter={(n) => String(n)}
        selected={false}
        onToggle={vi.fn()}
      />,
    )
    expect(screen.getByText('Women: 45')).toBeTruthy()
    expect(screen.getByText('Other: 0')).toBeTruthy()
    expect(screen.getByText('Men: 32')).toBeTruthy()
  })

  it('falls back to "Series N" for missing series names (1-indexed)', () => {
    render(
      <CategoryRowStacked
        name='Cat'
        values={[45, 32]}
        colors={COLORS}
        seriesNames={[]}
        formatter={(n) => String(n)}
        selected={false}
        onToggle={vi.fn()}
      />,
    )
    expect(screen.getByText('Series 1: 45')).toBeTruthy()
    expect(screen.getByText('Series 2: 32')).toBeTruthy()
  })

  it('per-row normalization: bar fills 100% with segments proportional to the row sum', () => {
    // values [30, 70] sum to 100 → segments at 30% and 70%, cumulative
    // offsets 0% and 30%. The bar saturates regardless of any
    // hypothetical global max — that's the point of stacked mode.
    const { container } = render(
      <CategoryRowStacked
        name='Cat'
        values={[30, 70]}
        colors={COLORS}
        seriesNames={['A', 'B']}
        formatter={(n) => String(n)}
        selected={false}
        onToggle={vi.fn()}
      />,
    )
    const fills = container.querySelectorAll<HTMLElement>(
      '[data-bar-fill="true"]',
    )
    expect(fills.length).toBe(2)
    expect(fills[0]!.style.left).toBe('0%')
    expect(fills[0]!.style.width).toBe('30%')
    expect(fills[1]!.style.left).toBe('30%')
    expect(fills[1]!.style.width).toBe('70%')
  })

  it('per-row normalization: bar still fills 100% for an arbitrary row total (45 + 32 → 77)', () => {
    const { container } = render(
      <CategoryRowStacked
        name='Cat'
        values={[45, 32]}
        colors={COLORS}
        seriesNames={['Women', 'Men']}
        formatter={(n) => String(n)}
        selected={false}
        onToggle={vi.fn()}
      />,
    )
    const fills = container.querySelectorAll<HTMLElement>(
      '[data-bar-fill="true"]',
    )
    // 45/77 ≈ 58.44…, 32/77 ≈ 41.56…
    const w0 = parseFloat(fills[0]!.style.width)
    const w1 = parseFloat(fills[1]!.style.width)
    expect(w0 + w1).toBeCloseTo(100, 1)
    expect(w0).toBeGreaterThan(58)
    expect(w0).toBeLessThan(59)
    expect(w1).toBeGreaterThan(41)
    expect(w1).toBeLessThan(42)
  })

  it('renders the stacked bar with one data-bar-fill per non-zero series', () => {
    const { container } = render(
      <CategoryRowStacked
        name='Cat'
        values={[45, 0, 32]}
        colors={[...COLORS, 'rgb(0, 50, 0)']}
        seriesNames={['Women', 'Other', 'Men']}
        formatter={(n) => String(n)}
        selected={false}
        onToggle={vi.fn()}
      />,
    )
    const fills = container.querySelectorAll('[data-bar-fill="true"]')
    // Zero series is skipped in the bar, present in the breakdown.
    expect(fills).toHaveLength(2)
  })

  it('clicking anywhere in the row toggles by raw name', () => {
    const onToggle = vi.fn()
    render(
      <CategoryRowStacked
        name='raw-key'
        displayName='Pretty'
        values={[45, 32]}
        colors={COLORS}
        seriesNames={['Women', 'Men']}
        formatter={(n) => String(n)}
        selected={false}
        onToggle={onToggle}
      />,
    )
    fireEvent.click(screen.getByRole('button'))
    expect(onToggle).toHaveBeenCalledWith('raw-key')
  })

  it('toggles via keyboard Enter and Space, preventing default on space', () => {
    const onToggle = vi.fn()
    render(
      <CategoryRowStacked
        name='K'
        values={[1]}
        colors={['rgb(0, 0, 0)']}
        seriesNames={['S']}
        formatter={(n) => String(n)}
        selected={false}
        onToggle={onToggle}
      />,
    )
    const row = screen.getByRole('button')
    fireEvent.keyDown(row, { key: 'Enter' })
    expect(onToggle).toHaveBeenCalledWith('K')
    onToggle.mockClear()
    fireEvent.keyDown(row, { key: ' ' })
    expect(onToggle).toHaveBeenCalledWith('K')
  })

  it('reflects selection via aria-pressed', () => {
    const { rerender } = render(
      <CategoryRowStacked
        name='K'
        values={[1]}
        colors={['rgb(0, 0, 0)']}
        seriesNames={['S']}
        formatter={(n) => String(n)}
        selected={false}
        onToggle={vi.fn()}
      />,
    )
    expect(screen.getByRole('button').getAttribute('aria-pressed')).toBe(
      'false',
    )
    rerender(
      <CategoryRowStacked
        name='K'
        values={[1]}
        colors={['rgb(0, 0, 0)']}
        seriesNames={['S']}
        formatter={(n) => String(n)}
        selected={true}
        onToggle={vi.fn()}
      />,
    )
    expect(screen.getByRole('button').getAttribute('aria-pressed')).toBe('true')
  })

  it('when dim colors are passed in, every segment paints with the dim color (breakdown text unchanged)', () => {
    const DIM = 'rgba(0, 0, 0, 0.26)'
    render(
      <CategoryRowStacked
        name='Cat'
        values={[45, 32]}
        colors={[DIM, DIM]}
        seriesNames={['Women', 'Men']}
        formatter={(n) => String(n)}
        selected={false}
        onToggle={vi.fn()}
      />,
    )
    const row = screen.getByRole('button')
    const fills = row.querySelectorAll<HTMLElement>('[data-bar-fill="true"]')
    expect(fills.length).toBe(2)
    fills.forEach((f) => expect(f.style.backgroundColor).toBe(DIM))
    // Breakdown text remains visible and unchanged.
    expect(within(row).getByText('Women: 45')).toBeTruthy()
    expect(within(row).getByText('Men: 32')).toBeTruthy()
  })
})
