import { TestBed } from '@angular/core/testing';
import { BarChart } from './bar-chart';

interface SampleRow {
  readonly day: string;
  readonly count: number;
}

const SAMPLE: readonly SampleRow[] = [
  { day: '2026-05-13', count: 12 },
  { day: '2026-05-14', count: 18 },
  { day: '2026-05-15', count: 7 },
];

function render(overrides: Partial<{ description: string }> = {}) {
  TestBed.configureTestingModule({ imports: [BarChart] });
  const fixture = TestBed.createComponent<BarChart<SampleRow>>(BarChart);
  fixture.componentRef.setInput('data', SAMPLE);
  fixture.componentRef.setInput('xKey', 'day');
  fixture.componentRef.setInput('yKey', 'count');
  fixture.componentRef.setInput('caption', 'Daily event volume');
  fixture.componentRef.setInput(
    'description',
    overrides.description ??
      'Bar chart showing the count of audit events emitted on each of the last three days.',
  );
  fixture.componentRef.setInput('ariaLabel', 'Daily event volume — bar chart');
  fixture.detectChanges();
  return fixture;
}

describe('BarChart', () => {
  beforeEach(() => TestBed.resetTestingModule());

  it('wraps the chart in a <figure role="img"> with aria-labelledby + aria-describedby', () => {
    const fixture = render();
    const figure = (fixture.nativeElement as HTMLElement).querySelector('figure.chart-figure');
    expect(figure?.getAttribute('role')).toBe('img');
    expect(figure?.getAttribute('aria-labelledby')).toMatch(/^chart-\d+-title$/);
    expect(figure?.getAttribute('aria-describedby')).toMatch(/^chart-\d+-desc$/);
  });

  it('renders the caption and screen-reader description from inputs', () => {
    const fixture = render();
    const root = fixture.nativeElement as HTMLElement;
    expect(root.querySelector('.chart-caption')?.textContent?.trim()).toBe('Daily event volume');
    expect(root.querySelector('.chart-description')?.textContent?.trim()).toContain(
      'Bar chart showing the count of audit events',
    );
  });

  it('renders a <details> tabular fallback with one row per data point', () => {
    const fixture = render();
    const root = fixture.nativeElement as HTMLElement;
    const fallback = root.querySelector('details.chart-fallback');
    expect(fallback).not.toBeNull();
    const rows = fallback?.querySelectorAll('tbody tr');
    expect(rows?.length).toBe(SAMPLE.length);
    expect(rows?.[0]?.querySelector('th')?.textContent?.trim()).toBe(SAMPLE[0]?.day);
    expect(rows?.[0]?.querySelector('td')?.textContent?.trim()).toBe(String(SAMPLE[0]?.count));
  });

  it('emits an SVG with <title> and <desc> as the first two children (a11y contract)', () => {
    const fixture = render();
    const root = fixture.nativeElement as HTMLElement;
    const svg = root.querySelector<SVGSVGElement>('.chart-canvas svg');
    expect(svg).not.toBeNull();
    const firstTwo = Array.from(svg?.children ?? []).slice(0, 2);
    expect(firstTwo[0]?.tagName).toBe('title');
    expect(firstTwo[1]?.tagName).toBe('desc');
    expect(firstTwo[0]?.textContent).toBe('Daily event volume');
    expect(firstTwo[1]?.textContent).toContain('Bar chart showing the count');
  });

  it('fills every bar with the same single colour (no per-bar palette cycle)', () => {
    const fixture = render();
    const root = fixture.nativeElement as HTMLElement;
    const svg = root.querySelector<SVGSVGElement>('.chart-canvas svg');
    expect(svg).not.toBeNull();
    // Plot applies a literal-string `fill` either on the bar-mark
    // `<g>` group or on each `<rect>`. Collect every `[fill]` attr
    // present below the mark group and confirm there's only one
    // distinct value (i.e. we are no longer cycling a categorical
    // palette across the X axis). Axes / rules carry their own
    // fills (text, ticks), so we narrow to the bar-mark subtree
    // via `aria-label`, which Plot sets to "bar" on the bar group.
    const barGroup = svg?.querySelector('[aria-label="bar"]');
    expect(barGroup).not.toBeNull();
    const fills = new Set<string>();
    const groupFill = barGroup?.getAttribute('fill');
    if (groupFill) fills.add(groupFill);
    barGroup
      ?.querySelectorAll<SVGElement>('[fill]')
      .forEach((el) => fills.add(el.getAttribute('fill') ?? ''));
    expect(fills.size).toBe(1);
  });

  it('regenerates the chart when `data` changes', async () => {
    const fixture = render();
    const root = fixture.nativeElement as HTMLElement;
    const before = root.querySelector('.chart-canvas svg');
    expect(before).not.toBeNull();

    fixture.componentRef.setInput('data', [
      { day: '2026-05-13', count: 99 },
      { day: '2026-05-14', count: 1 },
    ]);
    fixture.detectChanges();
    await fixture.whenStable();

    // The fallback table reflects the new dataset.
    const rows = root.querySelectorAll('details.chart-fallback tbody tr');
    expect(rows.length).toBe(2);
    expect(rows[0]?.querySelector('td')?.textContent?.trim()).toBe('99');
  });
});
