import { render } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { IsotypeDiagonal } from './IsotypeDiagonal';
import { ISOTYPE_COLORS } from './patterns';
import React from 'react';

type Point = [number, number];

function shapeWithFill(container: HTMLElement, fill: string) {
  return container.querySelector(`[fill="${fill}"]`)!;
}

function rectCorners(rect: Element): Point[] {
  const x = Number(rect.getAttribute('x'));
  const y = Number(rect.getAttribute('y'));
  const width = Number(rect.getAttribute('width'));
  const height = Number(rect.getAttribute('height'));
  return [
    [x, y],
    [x + width, y],
    [x + width, y + height],
    [x, y + height],
  ];
}

function polygonPoints(polygon: Element): Point[] {
  return polygon
    .getAttribute('points')!
    .trim()
    .split(/\s+/)
    .map(pair => pair.split(',').map(Number) as Point);
}

function shapePoints(shape: Element): Point[] {
  return shape.tagName === 'rect' ? rectCorners(shape) : polygonPoints(shape);
}

function shapeEdges(shape: Element): [Point, Point][] {
  const points = shapePoints(shape);
  return points.map((point, i) => [point, points[(i + 1) % points.length]]);
}

/** True if `shape` has an edge between `a` and `b`, in either direction. */
function hasEdge(shape: Element, a: Point, b: Point): boolean {
  return shapeEdges(shape).some(
    ([p, q]) =>
      (p[0] === a[0] && p[1] === a[1] && q[0] === b[0] && q[1] === b[1]) ||
      (p[0] === b[0] && p[1] === b[1] && q[0] === a[0] && q[1] === a[1])
  );
}

describe('IsotypeDiagonal', () => {
  it('renders 4 rects (blue, red, purple, brown) and 4 polygons (green, magenta, yellow, blank)', () => {
    const { container } = render(<IsotypeDiagonal />);
    expect(container.querySelectorAll('rect')).toHaveLength(4);
    expect(container.querySelectorAll('polygon')).toHaveLength(4);
  });

  it('uses every ISOTYPE_COLORS value exactly once as a fill', () => {
    const { container } = render(<IsotypeDiagonal />);
    Object.values(ISOTYPE_COLORS).forEach(color => {
      expect(container.querySelectorAll(`[fill="${color}"]`)).toHaveLength(1);
    });
  });

  it('renders exactly one shape with fill="none"', () => {
    const { container } = render(<IsotypeDiagonal />);
    expect(container.querySelectorAll('[fill="none"]')).toHaveLength(1);
  });

  it('renders no shape with a stroke', () => {
    const { container } = render(<IsotypeDiagonal />);
    container.querySelectorAll('rect, polygon').forEach(shape => {
      expect(shape.getAttribute('stroke')).toBeNull();
    });
  });

  it('passes className through to the root', () => {
    const { container } = render(<IsotypeDiagonal className="w-64" />);
    expect((container.firstChild as HTMLElement).className).toContain('w-64');
  });

  it('is decorative by default (aria-hidden, no role)', () => {
    const { container } = render(<IsotypeDiagonal />);
    const root = container.firstChild as HTMLElement;
    expect(root.getAttribute('aria-hidden')).toBe('true');
    expect(root.getAttribute('role')).toBeNull();
  });

  it('renders as an image with a label when aria-label is set', () => {
    const { container } = render(<IsotypeDiagonal aria-label="Xertica isotype" />);
    const root = container.firstChild as HTMLElement;
    expect(root.getAttribute('role')).toBe('img');
    expect(root.getAttribute('aria-label')).toBe('Xertica isotype');
    expect(root.getAttribute('aria-hidden')).toBeNull();
  });

  it('uses a 0 0 100 100 viewBox', () => {
    const { container } = render(<IsotypeDiagonal />);
    expect(container.querySelector('svg')!.getAttribute('viewBox')).toBe('0 0 100 100');
  });

  it('backs blue and red with full-width rectangles, wider than the triangular area they end up showing', () => {
    const { container } = render(<IsotypeDiagonal />);
    const blue = shapeWithFill(container, ISOTYPE_COLORS.blue);
    const red = shapeWithFill(container, ISOTYPE_COLORS.red);

    expect(blue.tagName).toBe('rect');
    expect(red.tagName).toBe('rect');
    expect(rectCorners(blue)).toEqual([
      [0, 0],
      [100, 0],
      [100, 25],
      [0, 25],
    ]);
    expect(rectCorners(red)).toEqual([
      [0, 75],
      [100, 75],
      [100, 100],
      [0, 100],
    ]);
  });

  it('paints purple/green after blue, and magenta/brown after red, so they overlap their backing rectangle instead of abutting it', () => {
    const { container } = render(<IsotypeDiagonal />);
    const shapes = Array.from(container.querySelectorAll('rect, polygon'));
    const indexOf = (color: string) => shapes.indexOf(shapeWithFill(container, color));

    expect(indexOf(ISOTYPE_COLORS.blue)).toBeLessThan(indexOf(ISOTYPE_COLORS.purple));
    expect(indexOf(ISOTYPE_COLORS.blue)).toBeLessThan(indexOf(ISOTYPE_COLORS.green));
    expect(indexOf(ISOTYPE_COLORS.red)).toBeLessThan(indexOf(ISOTYPE_COLORS.magenta));
    expect(indexOf(ISOTYPE_COLORS.red)).toBeLessThan(indexOf(ISOTYPE_COLORS.brown));
  });

  it('keeps purple and brown fully within the blue/red rectangles they sit on top of', () => {
    const { container } = render(<IsotypeDiagonal />);
    const purple = shapeWithFill(container, ISOTYPE_COLORS.purple);
    const brown = shapeWithFill(container, ISOTYPE_COLORS.brown);

    expect(Number(purple.getAttribute('y'))).toBeGreaterThanOrEqual(0);
    expect(Number(purple.getAttribute('y')) + Number(purple.getAttribute('height'))).toBeLessThanOrEqual(25);

    expect(Number(brown.getAttribute('y'))).toBeGreaterThanOrEqual(75);
    expect(Number(brown.getAttribute('y')) + Number(brown.getAttribute('height'))).toBeLessThanOrEqual(100);
  });

  it('keeps the untouched seams — purple/magenta, magenta/blank, yellow/green, and brown/green — matching exactly', () => {
    const { container } = render(<IsotypeDiagonal />);
    const purple = shapeWithFill(container, ISOTYPE_COLORS.purple);
    const magenta = shapeWithFill(container, ISOTYPE_COLORS.magenta);
    const blank = shapeWithFill(container, 'none');
    const yellow = shapeWithFill(container, ISOTYPE_COLORS.yellow);
    const green = shapeWithFill(container, ISOTYPE_COLORS.green);
    const brown = shapeWithFill(container, ISOTYPE_COLORS.brown);

    expect(hasEdge(purple, [0, 25], [25, 25])).toBe(true);
    expect(hasEdge(magenta, [0, 25], [25, 25])).toBe(true);

    expect(hasEdge(magenta, [25, 25], [25, 75])).toBe(true);
    expect(hasEdge(blank, [25, 25], [25, 75])).toBe(true);

    expect(hasEdge(yellow, [75, 25], [75, 75])).toBe(true);
    expect(hasEdge(green, [75, 25], [75, 75])).toBe(true);

    expect(hasEdge(brown, [75, 75], [100, 75])).toBe(true);
    expect(hasEdge(green, [75, 75], [100, 75])).toBe(true);
  });

  it('traces a single continuous diagonal through the blue/green tip, the magenta/red tip, and the yellow hypotenuse', () => {
    const diagonalVertices: Point[] = [
      [100, 0],
      [0, 100],
      [75, 25],
      [25, 75],
    ];
    diagonalVertices.forEach(([x, y]) => {
      expect(x + y).toBe(100);
    });
  });
});
